2016-06-15 21:50:56 +10:00
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
2017-03-27 22:15:02 +10:00
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2016-06-15 21:50:56 +10:00
// released under the MIT license
2012-12-08 22:54:58 -08:00
package irc
2014-02-22 12:49:33 -08:00
import (
2016-10-11 23:51:46 +10:00
"fmt"
2014-02-22 12:49:33 -08:00
"strconv"
2019-01-02 17:52:36 -05:00
"strings"
2016-06-26 21:06:28 +10:00
"time"
2016-10-16 12:54:15 +10:00
2017-01-11 02:09:08 +10:00
"sync"
2021-06-18 02:41:57 -04:00
"github.com/ergochat/irc-go/ircutils"
2021-03-18 03:49:12 -04:00
2021-05-25 00:34:38 -04:00
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
2017-11-08 22:19:50 -05:00
)
2020-02-18 19:38:42 -05:00
type ChannelSettings struct {
2021-01-20 21:13:18 -05:00
History HistoryStatus
QueryCutoff HistoryCutoff
2020-02-18 19:38:42 -05:00
}
2017-04-16 11:31:33 +10:00
// Channel represents a channel that clients can join.
2012-12-08 22:54:58 -08:00
type Channel struct {
2019-03-11 19:24:45 -04:00
flags modes . ModeSet
2018-02-03 20:21:32 +10:00
lists map [ modes . Mode ] * UserMaskSet
2017-11-07 14:38:18 -05:00
key string
2020-12-14 05:00:21 -05:00
forward string
2017-11-07 14:38:18 -05:00
members MemberSet
2018-04-24 05:46:01 -04:00
membersCache [ ] * Client // allow iteration over channel members without holding the lock
2017-11-07 14:38:18 -05:00
name string
nameCasefolded string
server * Server
createdTime time . Time
2017-11-08 22:19:50 -05:00
registeredFounder string
registeredTime time . Time
2019-12-16 19:50:15 -05:00
transferPendingTo string
2017-11-07 14:38:18 -05:00
topic string
topicSetBy string
topicSetTime time . Time
2018-12-28 13:45:55 -05:00
userLimit int
2018-04-03 21:49:40 -04:00
accountToUMode map [ string ] modes . Mode
2018-11-26 05:23:27 -05:00
history history . Buffer
2019-04-15 11:13:13 -04:00
stateMutex sync . RWMutex // tier 1
writerSemaphore utils . Semaphore // tier 1.5
joinPartMutex sync . Mutex // tier 3
ensureLoaded utils . Once // manages loading stored registration info from the database
2019-03-11 19:24:45 -04:00
dirtyBits uint
2020-02-18 19:38:42 -05:00
settings ChannelSettings
2012-12-08 22:54:58 -08:00
}
2014-02-04 19:28:24 -08:00
// NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server.
2019-12-17 13:21:26 -05:00
func NewChannel ( s * Server , name , casefoldedName string , registered bool ) * Channel {
2019-12-16 19:50:15 -05:00
config := s . Config ( )
2016-10-11 23:51:46 +10:00
2012-12-15 14:34:20 -08:00
channel := & Channel {
2021-04-07 08:44:17 -04:00
createdTime : time . Now ( ) . UTC ( ) , // may be overwritten by applyRegInfo
members : make ( MemberSet ) ,
name : name ,
nameCasefolded : casefoldedName ,
server : s ,
writerSemaphore : utils . NewSemaphore ( 1 ) ,
2012-12-09 12:51:50 -08:00
}
2014-02-25 11:11:34 -08:00
2019-12-16 19:50:15 -05:00
channel . initializeLists ( )
2020-02-18 19:38:42 -05:00
channel . history . Initialize ( 0 , 0 )
2019-03-11 19:24:45 -04:00
if ! registered {
2020-02-18 19:38:42 -05:00
channel . resizeHistory ( config )
2018-11-26 05:23:27 -05:00
for _ , mode := range config . Channels . defaultModes {
2018-04-22 18:47:10 -04:00
channel . flags . SetMode ( mode , true )
2016-04-21 19:29:50 +10:00
}
2019-03-11 19:24:45 -04:00
// no loading to do, so "mark" the load operation as "done":
channel . ensureLoaded . Do ( func ( ) { } )
} // else: modes will be loaded before first join
2018-11-26 05:23:27 -05:00
2012-12-15 14:34:20 -08:00
return channel
2012-12-08 22:54:58 -08:00
}
2019-12-16 19:50:15 -05: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 )
}
2019-03-11 19:24:45 -04:00
// EnsureLoaded blocks until the channel's registration info has been loaded
// from the database.
func ( channel * Channel ) EnsureLoaded ( ) {
channel . ensureLoaded . Do ( func ( ) {
nmc := channel . NameCasefolded ( )
info , err := channel . server . channelRegistry . LoadChannel ( nmc )
if err == nil {
channel . applyRegInfo ( info )
} else {
channel . server . logger . Error ( "internal" , "couldn't load channel" , nmc , err . Error ( ) )
}
} )
}
func ( channel * Channel ) IsLoaded ( ) bool {
return channel . ensureLoaded . Done ( )
}
2020-02-18 19:38:42 -05:00
func ( channel * Channel ) resizeHistory ( config * Config ) {
2021-01-20 21:13:18 -05:00
status , _ , _ := channel . historyStatus ( config )
2020-02-24 14:09:00 -05:00
if status == HistoryEphemeral {
2020-05-19 07:57:44 -04:00
channel . history . Resize ( config . History . ChannelLength , time . Duration ( config . History . AutoresizeWindow ) )
2020-02-18 19:38:42 -05:00
} else {
channel . history . Resize ( 0 , 0 )
}
}
2017-11-08 22:19:50 -05:00
// read in channel state that was persisted in the DB
2019-03-11 19:24:45 -04:00
func ( channel * Channel ) applyRegInfo ( chanReg RegisteredChannel ) {
2020-02-18 19:38:42 -05:00
defer channel . resizeHistory ( channel . server . Config ( ) )
2019-03-11 19:24:45 -04:00
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
2017-11-08 22:19:50 -05: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-03 21:49:40 -04:00
channel . key = chanReg . Key
2020-01-08 02:14:41 -05:00
channel . userLimit = chanReg . UserLimit
2020-02-18 19:38:42 -05:00
channel . settings = chanReg . Settings
2020-12-14 05:00:21 -05:00
channel . forward = chanReg . Forward
2018-04-03 21:49:40 -04:00
for _ , mode := range chanReg . Modes {
2018-04-22 18:47:10 -04:00
channel . flags . SetMode ( mode , true )
2018-04-03 21:49:40 -04:00
}
for account , mode := range chanReg . AccountToUMode {
channel . accountToUMode [ account ] = mode
}
2019-10-10 04:17:44 -04:00
channel . lists [ modes . BanMask ] . SetMasks ( chanReg . Bans )
channel . lists [ modes . InviteMask ] . SetMasks ( chanReg . Invites )
channel . lists [ modes . ExceptMask ] . SetMasks ( chanReg . Excepts )
2017-11-08 22:19:50 -05:00
}
// obtain a consistent snapshot of the channel state that can be persisted to the DB
2018-04-03 21:49:40 -04:00
func ( channel * Channel ) ExportRegistration ( includeFlags uint ) ( info RegisteredChannel ) {
2017-11-08 22:19:50 -05:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
info . Name = channel . name
2019-03-11 19:24:45 -04:00
info . NameCasefolded = channel . nameCasefolded
2017-11-08 22:19:50 -05:00
info . Founder = channel . registeredFounder
info . RegisteredAt = channel . registeredTime
2018-04-03 21:49:40 -04:00
if includeFlags & IncludeTopic != 0 {
info . Topic = channel . topic
info . TopicSetBy = channel . topicSetBy
info . TopicSetTime = channel . topicSetTime
}
if includeFlags & IncludeModes != 0 {
info . Key = channel . key
2020-12-14 05:00:21 -05:00
info . Forward = channel . forward
2018-04-22 18:47:10 -04:00
info . Modes = channel . flags . AllModes ( )
2020-01-08 02:14:41 -05:00
info . UserLimit = channel . userLimit
2018-04-03 21:49:40 -04:00
}
if includeFlags & IncludeLists != 0 {
2019-10-10 04:17:44 -04:00
info . Bans = channel . lists [ modes . BanMask ] . Masks ( )
info . Invites = channel . lists [ modes . InviteMask ] . Masks ( )
info . Excepts = channel . lists [ modes . ExceptMask ] . Masks ( )
2018-04-03 21:49:40 -04:00
info . AccountToUMode = make ( map [ string ] modes . Mode )
for account , mode := range channel . accountToUMode {
info . AccountToUMode [ account ] = mode
}
2017-11-08 22:19:50 -05:00
}
2020-02-18 19:38:42 -05:00
if includeFlags & IncludeSettings != 0 {
info . Settings = channel . settings
}
2017-11-08 22:19:50 -05:00
return
}
2019-03-11 19:24:45 -04: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 {
if ! channel . writerSemaphore . TryAcquire ( ) {
// a database write (which may fail) is in progress, the channel cannot be cleaned up
return false
}
defer channel . writerSemaphore . Release ( )
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2020-03-19 07:26:17 -04:00
if len ( channel . members ) != 0 {
return false
}
2021-02-04 15:26:03 -05:00
// see #1507 and #704 among others; registered channels should never be removed
return channel . registeredFounder == ""
2019-03-11 19:24:45 -04:00
}
func ( channel * Channel ) wakeWriter ( ) {
if channel . writerSemaphore . TryAcquire ( ) {
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 )
channel . writerSemaphore . Release ( )
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
if ! channel . writerSemaphore . TryAcquire ( ) {
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 )
}
} ( )
channel . writerSemaphore . Acquire ( )
defer channel . writerSemaphore . Release ( )
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
}
info := channel . ExportRegistration ( dirtyBits )
err = channel . server . channelRegistry . StoreChannel ( info , dirtyBits )
if err != nil {
channel . stateMutex . Lock ( )
channel . dirtyBits = channel . dirtyBits | dirtyBits
channel . stateMutex . Unlock ( )
}
return
}
2017-11-08 22:19:50 -05: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 22:03:36 +10:00
return errChannelAlreadyRegistered
2017-11-08 22:19:50 -05:00
}
channel . registeredFounder = founder
2019-05-12 03:12:50 -04:00
channel . registeredTime = time . Now ( ) . UTC ( )
2018-04-03 21:49:40 -04:00
channel . accountToUMode [ founder ] = modes . ChannelFounder
2017-11-08 22:19:50 -05:00
return nil
}
2018-06-04 05:02:22 -04:00
// SetUnregistered deletes the channel's registration information.
2019-02-11 23:30:49 -05:00
func ( channel * Channel ) SetUnregistered ( expectedFounder string ) {
2018-06-04 05:02:22 -04:00
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
2019-02-11 23:30:49 -05:00
if channel . registeredFounder != expectedFounder {
return
}
2018-06-04 05:02:22 -04:00
channel . registeredFounder = ""
var zeroTime time . Time
channel . registeredTime = zeroTime
channel . accountToUMode = make ( map [ string ] modes . Mode )
}
2019-12-16 19:50:15 -05: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-08 22:19:50 -05: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-16 19:50:15 -05: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 01:46:22 -05:00
channel . Store ( IncludeAllAttrs )
2019-12-16 19:50:15 -05: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 01:46:22 -05:00
channel . Store ( IncludeAllAttrs )
2019-12-16 19:50:15 -05: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 03:11:11 -04:00
func ( channel * Channel ) regenerateMembersCache ( ) {
channel . stateMutex . RLock ( )
2017-10-22 19:50:16 -04:00
result := make ( [ ] * Client , len ( channel . members ) )
i := 0
for client := range channel . members {
result [ i ] = client
i ++
}
2018-04-24 03:11:11 -04:00
channel . stateMutex . RUnlock ( )
channel . stateMutex . Lock ( )
2017-10-22 19:50:16 -04:00
channel . membersCache = result
2018-04-24 03:11:11 -04:00
channel . stateMutex . Unlock ( )
2012-12-09 21:46:22 -08:00
}
2017-03-27 14:29:51 +10:00
// Names sends the list of users joined to the channel to the given client.
2018-02-06 00:21:08 +10:00
func ( channel * Channel ) Names ( client * Client , rb * ResponseBuffer ) {
2020-10-02 08:13:52 -04:00
channel . stateMutex . RLock ( )
2021-01-20 21:13:18 -05:00
clientData , isJoined := channel . members [ client ]
2020-10-02 08:13:52 -04:00
channel . stateMutex . RUnlock ( )
2021-02-06 22:45:34 -05:00
isOper := client . HasRoleCapabs ( "sajoin" )
2020-10-02 08:13:52 -04:00
respectAuditorium := channel . flags . HasMode ( modes . Auditorium ) && ! isOper &&
2021-01-20 21:13:18 -05:00
( ! isJoined || clientData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) )
2019-04-12 00:08:46 -04:00
isMultiPrefix := rb . session . capabilities . Has ( caps . MultiPrefix )
isUserhostInNames := rb . session . capabilities . Has ( caps . UserhostInNames )
2018-04-24 05:46:01 -04:00
maxNamLen := 480 - len ( client . server . name ) - len ( client . Nick ( ) )
var namesLines [ ] string
2020-06-08 23:38:10 -04:00
var buffer strings . Builder
2019-04-28 21:09:56 -04:00
if isJoined || ! channel . flags . HasMode ( modes . Secret ) || isOper {
2019-04-23 01:52:26 -04:00
for _ , target := range channel . Members ( ) {
var nick string
if isUserhostInNames {
nick = target . NickMaskString ( )
} else {
nick = target . Nick ( )
}
channel . stateMutex . RLock ( )
2021-01-20 21:13:18 -05:00
memberData , _ := channel . members [ target ]
2019-04-23 01:52:26 -04:00
channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
modeSet := memberData . modes
2019-04-23 01:52:26 -04:00
if modeSet == nil {
continue
}
2020-02-18 19:38:42 -05:00
if ! isJoined && target . HasMode ( modes . Invisible ) && ! isOper {
2019-04-23 01:52:26 -04:00
continue
}
2020-10-02 08:13:52 -04:00
if respectAuditorium && modeSet . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
continue
}
2019-04-23 01:52:26 -04:00
prefix := modeSet . Prefixes ( isMultiPrefix )
if buffer . Len ( ) + len ( nick ) + len ( prefix ) + 1 > maxNamLen {
namesLines = append ( namesLines , buffer . String ( ) )
buffer . Reset ( )
}
if buffer . Len ( ) > 0 {
buffer . WriteString ( " " )
}
buffer . WriteString ( prefix )
buffer . WriteString ( nick )
2018-04-24 05:46:01 -04:00
}
if buffer . Len ( ) > 0 {
2019-04-23 01:52:26 -04:00
namesLines = append ( namesLines , buffer . String ( ) )
2018-04-24 05:46:01 -04:00
}
2016-06-19 14:55:24 +10:00
}
2018-04-24 05:46:01 -04:00
for _ , line := range namesLines {
if buffer . Len ( ) > 0 {
rb . Add ( nil , client . server . name , RPL_NAMREPLY , client . nick , "=" , channel . name , line )
}
}
2018-02-06 00:21:08 +10:00
rb . Add ( nil , client . server . name , RPL_ENDOFNAMES , client . nick , channel . name , client . t ( "End of NAMES list" ) )
2013-06-02 22:07:50 -07:00
}
2019-04-23 00:05:12 -04: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-23 18:33:41 -04:00
return targetMode == modes . Voice || targetMode == modes . Mode ( 0 )
2019-04-23 00:05:12 -04:00
default :
// voice and unprivileged cannot kick anyone
2018-08-17 12:44:49 -04:00
return false
}
2019-04-23 00:05:12 -04: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 ( )
2021-01-20 21:13:18 -05:00
memberData := channel . members [ client ]
2020-05-10 23:17:09 -04:00
founder := channel . registeredFounder
2019-04-23 00:05:12 -04:00
channel . stateMutex . RUnlock ( )
2018-04-22 18:47:10 -04:00
2020-05-10 23:17:09 -04:00
if founder != "" && founder == client . Account ( ) {
return true
}
2018-05-23 15:35:50 -04:00
for _ , mode := range modes . ChannelUserModes {
2021-01-20 21:13:18 -05:00
if memberData . modes . HasMode ( mode ) {
2016-10-23 00:45:51 +10:00
return true
}
if mode == permission {
break
}
}
return false
2014-02-09 19:59:59 -08:00
}
2017-10-22 19:50:16 -04:00
func ( channel * Channel ) ClientPrefixes ( client * Client , isMultiPrefix bool ) string {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
memberData , present := channel . members [ client ]
2017-10-22 19:50:16 -04:00
if ! present {
return ""
} else {
2021-01-20 21:13:18 -05:00
return memberData . modes . Prefixes ( isMultiPrefix )
2017-10-22 19:50:16 -04:00
}
}
2021-01-20 21:13:18 -05:00
func ( channel * Channel ) ClientStatus ( client * Client ) ( present bool , joinTimeSecs int64 , cModes modes . Modes ) {
2020-04-15 18:14:17 +10:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
memberData , present := channel . members [ client ]
return present , time . Unix ( 0 , memberData . joinTime ) . Unix ( ) , memberData . modes . AllModes ( )
2020-04-15 18:14:17 +10:00
}
2020-12-02 03:56:00 -05: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-20 21:13:18 -05:00
func ( channel * Channel ) alwaysOnStatus ( client * Client ) ( chname string , status alwaysOnChannelStatus ) {
2020-12-02 03:56:00 -05:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
chname = channel . name
2021-01-20 21:13:18 -05:00
data := channel . members [ client ]
status . Modes = data . modes . String ( )
status . JoinTime = data . joinTime
2020-12-02 03:56:00 -05:00
return
}
// overwrite any existing channel-user modes with the stored ones
2021-01-20 21:13:18 -05:00
func ( channel * Channel ) setMemberStatus ( client * Client , status alwaysOnChannelStatus ) {
2020-12-02 03:56:00 -05:00
newModes := modes . NewModeSet ( )
2021-01-20 21:13:18 -05:00
for _ , mode := range status . Modes {
2020-12-02 03:56:00 -05:00
newModes . SetMode ( modes . Mode ( mode ) , true )
}
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
if _ , ok := channel . members [ client ] ; ! ok {
return
}
2021-01-20 21:13:18 -05:00
memberData := channel . members [ client ]
memberData . modes = newModes
memberData . joinTime = status . JoinTime
channel . members [ client ] = memberData
2020-12-02 03:56:00 -05:00
}
2017-10-22 19:50:16 -04:00
func ( channel * Channel ) ClientHasPrivsOver ( client * Client , target * Client ) bool {
channel . stateMutex . RLock ( )
2020-04-22 21:52:24 -04:00
founder := channel . registeredFounder
2021-01-20 21:13:18 -05:00
clientModes := channel . members [ client ] . modes
targetModes := channel . members [ target ] . modes
2019-02-13 14:38:10 -05:00
channel . stateMutex . RUnlock ( )
2020-09-09 23:15:00 -04: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-22 21:52:24 -04:00
}
2019-04-23 00:05:12 -04:00
return channelUserModeHasPrivsOver ( clientModes . HighestChannelUserMode ( ) , targetModes . HighestChannelUserMode ( ) )
2017-10-22 19:50:16 -04:00
}
func ( channel * Channel ) hasClient ( client * Client ) bool {
channel . stateMutex . RLock ( )
_ , present := channel . members [ client ]
2019-04-23 00:05:12 -04:00
channel . stateMutex . RUnlock ( )
2017-10-22 19:50:16 -04:00
return present
2014-02-04 19:28:24 -08:00
}
2014-02-08 18:14:39 -08:00
// <mode> <mode params>
2017-10-22 19:50:16 -04:00
func ( channel * Channel ) modeStrings ( client * Client ) ( result [ ] string ) {
2021-02-06 22:45:34 -05:00
hasPrivs := client . HasRoleCapabs ( "sajoin" )
2020-05-17 14:02:59 -04:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
isMember := hasPrivs || channel . members . Has ( client )
2014-02-22 12:49:33 -08:00
showKey := isMember && ( channel . key != "" )
showUserLimit := channel . userLimit > 0
2020-12-14 05:00:21 -05:00
showForward := channel . forward != ""
2014-02-22 12:49:33 -08:00
2020-12-14 05:00:21 -05:00
var mods strings . Builder
mods . WriteRune ( '+' )
2017-10-22 19:50:16 -04:00
2014-02-22 12:49:33 -08:00
// flags with args
if showKey {
2020-12-14 05:00:21 -05:00
mods . WriteRune ( rune ( modes . Key ) )
2014-02-08 23:33:56 -08:00
}
2014-02-22 12:49:33 -08:00
if showUserLimit {
2020-12-14 05:00:21 -05:00
mods . WriteRune ( rune ( modes . UserLimit ) )
}
if showForward {
mods . WriteRune ( rune ( modes . Forward ) )
2014-02-22 12:49:33 -08:00
}
2014-02-14 21:57:08 -08:00
2020-12-14 05:00:21 -05:00
for _ , m := range channel . flags . AllModes ( ) {
mods . WriteRune ( rune ( m ) )
}
2018-04-22 18:47:10 -04:00
2020-12-14 05:00:21 -05:00
result = [ ] string { mods . String ( ) }
2014-02-14 21:57:08 -08:00
2014-02-22 12:49:33 -08:00
// args for flags with args: The order must match above to keep
// positional arguments in place.
if showKey {
2017-10-22 19:50:16 -04:00
result = append ( result , channel . key )
2014-02-14 21:57:08 -08:00
}
2014-02-22 12:49:33 -08:00
if showUserLimit {
2018-12-28 13:45:55 -05:00
result = append ( result , strconv . Itoa ( channel . userLimit ) )
2014-02-22 12:49:33 -08:00
}
2020-12-14 05:00:21 -05:00
if showForward {
result = append ( result , channel . forward )
}
2014-02-14 21:57:08 -08:00
2017-10-22 19:50:16 -04:00
return
2014-02-08 18:14:39 -08:00
}
2017-10-30 05:21:47 -04:00
func ( channel * Channel ) IsEmpty ( ) bool {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
return len ( channel . members ) == 0
}
2020-02-18 19:38:42 -05:00
// figure out where history is being stored: persistent, ephemeral, or neither
// target is only needed if we're doing persistent history
2021-01-20 21:13:18 -05:00
func ( channel * Channel ) historyStatus ( config * Config ) ( status HistoryStatus , target string , restrictions HistoryCutoff ) {
2020-02-24 14:09:00 -05:00
if ! config . History . Enabled {
2021-01-20 21:13:18 -05:00
return HistoryDisabled , "" , HistoryCutoffNone
2020-02-18 19:38:42 -05:00
}
channel . stateMutex . RLock ( )
target = channel . nameCasefolded
2021-01-20 21:13:18 -05:00
settings := channel . settings
2020-02-18 19:38:42 -05:00
registered := channel . registeredFounder != ""
channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05: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 07:26:17 -04:00
}
func channelHistoryStatus ( config * Config , registered bool , storedStatus HistoryStatus ) ( result HistoryStatus ) {
if ! config . History . Enabled {
return HistoryDisabled
}
2020-02-18 19:38:42 -05: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 07:26:17 -04:00
return historyEnabled ( config . History . Persistent . RegisteredChannels , storedStatus )
2020-02-18 19:38:42 -05:00
} else {
2020-02-24 14:09:00 -05:00
if config . History . Persistent . UnregisteredChannels {
2020-03-19 07:26:17 -04:00
return HistoryPersistent
2020-02-24 14:09:00 -05:00
} else {
2020-03-19 07:26:17 -04:00
return HistoryEphemeral
2020-02-24 14:09:00 -05:00
}
2020-02-18 19:38:42 -05:00
}
}
2020-05-12 12:05:40 -04:00
func ( channel * Channel ) AddHistoryItem ( item history . Item , account string ) ( err error ) {
2020-07-09 18:36:45 -04:00
if ! itemIsStorable ( & item , channel . server . Config ( ) ) {
2020-02-18 19:38:42 -05:00
return
}
2021-01-20 21:13:18 -05:00
status , target , _ := channel . historyStatus ( channel . server . Config ( ) )
2020-02-24 14:09:00 -05:00
if status == HistoryPersistent {
2020-05-12 12:05:40 -04:00
err = channel . server . historyDB . AddChannelItem ( target , item , account )
2020-02-24 14:09:00 -05:00
} else if status == HistoryEphemeral {
2020-02-18 19:38:42 -05:00
channel . history . Add ( item )
}
2020-02-24 14:09:00 -05:00
return
2020-02-18 19:38:42 -05:00
}
2017-03-27 14:29:51 +10:00
// Join joins the given client to this channel (if they can be joined).
2020-12-14 05:00:21 -05:00
func ( channel * Channel ) Join ( client * Client , key string , isSajoin bool , rb * ResponseBuffer ) ( joinErr error , forward string ) {
2019-01-01 13:15:38 -05:00
details := client . Details ( )
2021-03-17 14:36:52 -04:00
isBot := client . HasMode ( modes . Bot )
2018-12-30 18:28:56 -05:00
2018-05-25 02:46:36 -04:00
channel . stateMutex . RLock ( )
chname := channel . name
2018-12-23 13:25:02 -05:00
chcfname := channel . nameCasefolded
2018-05-25 02:46:36 -04:00
founder := channel . registeredFounder
2020-10-25 20:40:41 -04:00
createdAt := channel . createdTime
2018-12-28 13:45:55 -05:00
chkey := channel . key
limit := channel . userLimit
chcount := len ( channel . members )
_ , alreadyJoined := channel . members [ client ]
2019-01-01 13:15:38 -05:00
persistentMode := channel . accountToUMode [ details . account ]
2020-12-14 05:00:21 -05:00
forward = channel . forward
2018-05-25 02:46:36 -04:00
channel . stateMutex . RUnlock ( )
2018-12-28 13:45:55 -05:00
if alreadyJoined {
// no message needs to be sent
2020-12-14 05:00:21 -05:00
return nil , ""
2018-12-28 13:45:55 -05:00
}
2020-06-30 19:24:56 -04: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-25 20:40:41 -04:00
client . CheckInvited ( chcfname , createdAt )
2020-06-30 19:24:56 -04:00
if ! hasPrivs {
if limit != 0 && chcount >= limit {
2020-12-14 05:00:21 -05:00
return errLimitExceeded , forward
2020-06-30 19:24:56 -04:00
}
2018-04-24 03:11:11 -04:00
2020-06-30 19:24:56 -04:00
if chkey != "" && ! utils . SecretTokensMatch ( chkey , key ) {
2020-12-14 05:00:21 -05:00
return errWrongChannelKey , forward
2020-06-30 19:24:56 -04:00
}
2014-02-22 12:49:33 -08:00
2020-06-30 19:24:56 -04:00
if channel . flags . HasMode ( modes . InviteOnly ) &&
! channel . lists [ modes . InviteMask ] . Match ( details . nickMaskCasefolded ) {
2020-12-14 05:00:21 -05:00
return errInviteOnly , forward
2020-06-30 19:24:56 -04:00
}
2014-02-20 18:56:13 -08:00
2020-06-30 19:24:56 -04: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 05:00:21 -05:00
// do not forward people who are banned:
return errBanned , ""
2020-06-30 19:24:56 -04:00
}
2014-03-07 17:09:49 -08:00
2020-07-08 05:32:14 -04:00
if details . account == "" &&
2021-12-15 22:52:11 -05:00
( channel . flags . HasMode ( modes . RegisteredOnly ) || channel . server . Defcon ( ) <= 2 ) &&
! channel . lists [ modes . InviteMask ] . Match ( details . nickMaskCasefolded ) {
2020-12-14 05:00:21 -05:00
return errRegisteredOnly , forward
2020-06-30 19:24:56 -04:00
}
2014-03-07 17:09:49 -08:00
}
2020-06-30 19:24:56 -04:00
if joinErr := client . addChannel ( channel , rb == nil ) ; joinErr != nil {
2020-12-14 05:00:21 -05:00
return joinErr , ""
2019-05-12 04:49:45 -04:00
}
2021-02-04 15:26:03 -05:00
client . server . logger . Debug ( "channels" , fmt . Sprintf ( "%s joined channel %s" , details . nick , chname ) )
2017-03-06 22:11:10 +10:00
2019-02-13 13:22:00 -05:00
givenMode := func ( ) ( givenMode modes . Mode ) {
2018-04-24 03:11:11 -04: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 13:22:00 -05:00
newChannel := firstJoin && channel . registeredFounder == ""
2018-04-24 03:11:11 -04:00
if newChannel {
givenMode = modes . ChannelOperator
2018-02-06 00:21:08 +10:00
} else {
2018-12-30 18:28:56 -05:00
givenMode = persistentMode
2018-02-06 00:21:08 +10:00
}
2018-04-24 03:11:11 -04:00
if givenMode != 0 {
2021-01-20 21:13:18 -05:00
channel . members [ client ] . modes . SetMode ( givenMode , true )
2018-02-06 00:21:08 +10:00
}
2018-04-24 03:11:11 -04:00
} ( )
2016-06-22 21:35:26 +10:00
2018-04-24 03:11:11 -04:00
channel . regenerateMembersCache ( )
2019-01-01 13:15:38 -05:00
2018-04-24 03:11:11 -04:00
return
} ( )
2017-10-22 19:50:16 -04:00
2020-02-20 02:57:39 -05:00
var message utils . SplitMessage
2020-10-02 08:13:52 -04:00
respectAuditorium := givenMode == modes . Mode ( 0 ) && channel . flags . HasMode ( modes . Auditorium )
2021-05-05 10:00:19 -04:00
message = utils . MakeMessage ( "" )
2020-02-20 02:57:39 -05:00
// no history item for fake persistent joins
2020-10-02 08:13:52 -04:00
if rb != nil && ! respectAuditorium {
2020-02-20 02:57:39 -05:00
histItem := history . Item {
Type : history . Join ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : message ,
2021-03-17 14:36:52 -04:00
IsBot : isBot ,
2020-02-20 02:57:39 -05:00
}
histItem . Params [ 0 ] = details . realname
2020-05-12 12:05:40 -04:00
channel . AddHistoryItem ( histItem , details . account )
2020-02-20 02:57:39 -05:00
}
2020-02-20 03:02:39 -05:00
if rb == nil {
2020-12-14 05:00:21 -05:00
return nil , ""
2020-02-20 03:02:39 -05:00
}
2018-04-24 03:11:11 -04:00
var modestr string
if givenMode != 0 {
modestr = fmt . Sprintf ( "+%v" , givenMode )
2017-11-08 22:19:50 -05:00
}
2018-04-24 03:11:11 -04:00
2020-11-27 00:13:47 -05:00
// cache the most common case (JOIN without extended-join)
var cache MessageCache
2021-03-17 14:36:52 -04:00
cache . Initialize ( channel . server , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname )
2020-07-17 01:55:13 -04:00
isAway , awayMessage := client . Away ( )
2018-04-24 03:11:11 -04:00
for _ , member := range channel . Members ( ) {
2020-10-02 08:13:52 -04:00
if respectAuditorium {
channel . stateMutex . RLock ( )
2021-01-20 21:13:18 -05:00
memberData , ok := channel . members [ member ]
2020-10-02 08:13:52 -04:00
channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
if ! ok || memberData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-02 08:13:52 -04:00
continue
}
}
2019-04-12 00:08:46 -04:00
for _ , session := range member . Sessions ( ) {
2020-02-20 03:02:39 -05:00
if session == rb . session {
2019-04-12 00:08:46 -04:00
continue
} else if client == session . client {
channel . playJoinForSession ( session )
continue
}
if session . capabilities . Has ( caps . ExtendedJoin ) {
2021-03-17 14:36:52 -04:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname , details . accountName , details . realname )
2019-04-12 00:08:46 -04:00
} else {
2020-11-27 00:13:47 -05:00
cache . Send ( session )
2019-04-12 00:08:46 -04:00
}
if givenMode != 0 {
session . Send ( nil , client . server . name , "MODE" , chname , modestr , details . nick )
}
2020-07-17 01:55:13 -04:00
if isAway && session . capabilities . Has ( caps . AwayNotify ) {
2021-03-17 14:36:52 -04:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . accountName , isBot , nil , "AWAY" , awayMessage )
2020-07-17 01:55:13 -04:00
}
2018-04-24 03:11:11 -04:00
}
2017-11-08 22:19:50 -05:00
}
2014-02-16 17:23:47 -08:00
2020-02-20 03:02:39 -05:00
if rb . session . capabilities . Has ( caps . ExtendedJoin ) {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname , details . accountName , details . realname )
2016-08-14 11:59:33 +10:00
} else {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname )
2016-08-14 11:59:33 +10:00
}
2018-04-24 03:11:11 -04:00
2020-02-20 03:02:39 -05:00
if rb . session . client == client {
2019-04-12 00:08:46 -04: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 11:57:30 -05: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 00:08:46 -04:00
}
2018-04-24 03:11:11 -04:00
2018-12-28 13:45:55 -05:00
// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
rb . Flush ( true )
2020-02-20 03:02:39 -05:00
channel . autoReplayHistory ( client , rb , message . Msgid )
2020-12-14 05:00:21 -05:00
return nil , ""
2019-05-29 19:23:46 -04:00
}
func ( channel * Channel ) autoReplayHistory ( client * Client , rb * ResponseBuffer , skipMsgid string ) {
2019-05-20 19:08:57 -04:00
// autoreplay any messages as necessary
var items [ ] history . Item
2020-02-18 19:38:42 -05:00
2020-07-21 16:33:17 -04:00
hasAutoreplayTimestamps := false
2020-02-27 19:07:49 -05:00
var start , end time . Time
2020-02-27 14:43:59 -05:00
if rb . session . zncPlaybackTimes . ValidFor ( channel . NameCasefolded ( ) ) {
2020-07-21 16:33:17 -04:00
hasAutoreplayTimestamps = true
2020-02-27 19:07:49 -05:00
start , end = rb . session . zncPlaybackTimes . start , rb . session . zncPlaybackTimes . end
2020-02-27 02:13:31 -05:00
} else if ! rb . session . autoreplayMissedSince . IsZero ( ) {
2020-02-18 19:38:42 -05:00
// we already checked for history caps in `playReattachMessages`
2020-07-21 16:33:17 -04:00
hasAutoreplayTimestamps = true
2020-02-27 19:07:49 -05:00
start = time . Now ( ) . UTC ( )
end = rb . session . autoreplayMissedSince
2020-02-18 19:38:42 -05:00
}
2020-07-21 16:33:17 -04:00
if hasAutoreplayTimestamps {
2021-11-01 01:23:07 -04:00
_ , seq , _ := channel . server . GetHistorySequence ( channel , client , "" )
2020-02-18 19:38:42 -05:00
if seq != nil {
zncMax := channel . server . Config ( ) . History . ZNCMax
2021-04-06 00:46:07 -04:00
items , _ = seq . Between ( history . Selector { Time : start } , history . Selector { Time : end } , zncMax )
2020-02-18 19:38:42 -05:00
}
2019-05-29 19:23:46 -04:00
} else if ! rb . session . HasHistoryCaps ( ) {
2019-05-20 19:08:57 -04: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 01:23:07 -04:00
_ , seq , _ := channel . server . GetHistorySequence ( channel , client , "" )
2020-02-18 19:38:42 -05:00
if seq != nil {
2021-04-06 00:46:07 -04:00
items , _ = seq . Between ( history . Selector { } , history . Selector { } , replayLimit )
2020-02-18 19:38:42 -05:00
}
2019-05-06 23:17:57 -04:00
}
2018-12-28 13:45:55 -05:00
}
2019-05-20 19:08:57 -04:00
// remove the client's own JOIN line from the replay
numItems := len ( items )
for i := len ( items ) - 1 ; 0 <= i ; i -- {
2019-05-29 19:23:46 -04:00
if items [ i ] . Message . Msgid == skipMsgid {
2019-05-20 19:08:57 -04: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 01:24:14 -04:00
channel . replayHistoryItems ( rb , items , false )
2019-05-20 19:08:57 -04:00
rb . Flush ( true )
}
2014-02-16 17:23:47 -08:00
}
2019-04-12 00:08:46 -04: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 16:10:56 -04:00
details := client . Details ( )
2019-04-12 00:08:46 -04:00
if session . capabilities . Has ( caps . ExtendedJoin ) {
2019-05-22 16:10:56 -04:00
sessionRb . Add ( nil , details . nickMask , "JOIN" , channel . Name ( ) , details . accountName , details . realname )
2019-04-12 00:08:46 -04:00
} else {
2019-05-22 16:10:56 -04:00
sessionRb . Add ( nil , details . nickMask , "JOIN" , channel . Name ( ) )
2019-04-12 00:08:46 -04:00
}
channel . SendTopic ( client , sessionRb , false )
channel . Names ( client , sessionRb )
sessionRb . Send ( false )
}
2017-03-27 14:29:51 +10:00
// Part parts the given client from this channel, with the given message.
2018-02-06 00:21:08 +10:00
func ( channel * Channel ) Part ( client * Client , message string , rb * ResponseBuffer ) {
2020-10-02 08:13:52 -04:00
channel . stateMutex . RLock ( )
chname := channel . name
2021-01-20 21:13:18 -05:00
clientData , ok := channel . members [ client ]
2020-10-02 08:13:52 -04:00
channel . stateMutex . RUnlock ( )
if ! ok {
2019-03-19 03:35:49 -04:00
rb . Add ( nil , client . server . name , ERR_NOTONCHANNEL , client . Nick ( ) , chname , client . t ( "You're not on that channel" ) )
2012-12-08 22:54:58 -08:00
return
}
2018-04-24 03:11:11 -04:00
channel . Quit ( client )
2020-01-18 23:47:05 -05:00
splitMessage := utils . MakeMessage ( message )
2019-05-06 23:17:57 -04:00
2019-01-01 13:15:38 -05:00
details := client . Details ( )
2021-03-17 14:36:52 -04:00
isBot := client . HasMode ( modes . Bot )
2019-12-02 21:13:09 -05:00
params := make ( [ ] string , 1 , 2 )
params [ 0 ] = chname
if message != "" {
params = append ( params , message )
}
2020-10-02 08:13:52 -04:00
respectAuditorium := channel . flags . HasMode ( modes . Auditorium ) &&
2021-01-20 21:13:18 -05:00
clientData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 )
2020-11-27 00:13:47 -05:00
var cache MessageCache
2021-03-17 14:36:52 -04:00
cache . Initialize ( channel . server , splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "PART" , params ... )
2017-10-22 19:50:16 -04:00
for _ , member := range channel . Members ( ) {
2020-10-02 08:13:52 -04:00
if respectAuditorium {
channel . stateMutex . RLock ( )
2021-01-20 21:13:18 -05:00
memberData , ok := channel . members [ member ]
2020-10-02 08:13:52 -04:00
channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
if ! ok || memberData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-02 08:13:52 -04:00
continue
}
}
2020-11-27 00:13:47 -05:00
for _ , session := range member . Sessions ( ) {
cache . Send ( session )
}
2014-02-19 22:20:34 -08:00
}
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "PART" , params ... )
2019-04-12 00:08:46 -04:00
for _ , session := range client . Sessions ( ) {
if session != rb . session {
2021-03-17 14:36:52 -04:00
session . sendFromClientInternal ( false , splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "PART" , params ... )
2019-04-12 00:08:46 -04:00
}
}
2017-03-06 22:11:10 +10:00
2020-10-02 08:13:52 -04:00
if ! respectAuditorium {
channel . AddHistoryItem ( history . Item {
Type : history . Part ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : splitMessage ,
2021-03-17 14:36:52 -04:00
IsBot : isBot ,
2020-10-02 08:13:52 -04:00
} , details . account )
}
2018-11-26 05:23:27 -05:00
2021-02-04 15:26:03 -05:00
client . server . logger . Debug ( "channels" , fmt . Sprintf ( "%s left channel %s" , details . nick , chname ) )
2012-12-08 22:54:58 -08:00
}
2021-11-01 01:24:14 -04:00
func ( channel * Channel ) replayHistoryItems ( rb * ResponseBuffer , items [ ] history . Item , chathistoryCommand bool ) {
2020-02-20 23:47:13 -05:00
// send an empty batch if necessary, as per the CHATHISTORY spec
2018-11-26 05:23:27 -05:00
chname := channel . Name ( )
2018-12-28 13:45:55 -05:00
client := rb . target
2019-05-06 23:17:57 -04:00
eventPlayback := rb . session . capabilities . Has ( caps . EventPlayback )
extendedJoin := rb . session . capabilities . Has ( caps . ExtendedJoin )
2019-12-18 17:38:14 -05:00
var playJoinsAsPrivmsg bool
if ! eventPlayback {
2021-11-01 01:24:14 -04:00
if chathistoryCommand {
2019-12-18 17:38:14 -05:00
playJoinsAsPrivmsg = true
2021-11-01 01:24:14 -04:00
} else {
switch client . AccountSettings ( ) . ReplayJoins {
case ReplayJoinsCommandsOnly :
playJoinsAsPrivmsg = false
case ReplayJoinsAlways :
playJoinsAsPrivmsg = true
}
2019-12-18 17:38:14 -05:00
}
2019-05-06 23:17:57 -04:00
}
2019-12-18 17:38:14 -05:00
2019-05-06 23:17:57 -04:00
batchID := rb . StartNestedHistoryBatch ( chname )
defer rb . EndNestedBatch ( batchID )
2018-12-28 13:45:55 -05:00
2019-05-06 23:17:57 -04:00
for _ , item := range items {
2020-12-14 15:23:01 -05:00
nick := NUHToNick ( item . Nick )
2018-11-26 05:23:27 -05:00
switch item . Type {
case history . Privmsg :
2021-03-17 14:36:52 -04:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , item . Tags , "PRIVMSG" , chname , item . Message )
2018-11-26 05:23:27 -05:00
case history . Notice :
2021-03-17 14:36:52 -04:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , item . Tags , "NOTICE" , chname , item . Message )
2019-05-06 23:17:57 -04:00
case history . Tagmsg :
2020-05-22 10:58:46 -04:00
if eventPlayback {
2021-03-17 14:36:52 -04:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , item . Tags , "TAGMSG" , chname , item . Message )
2021-11-01 01:24:14 -04: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-06 23:17:57 -04:00
}
2018-11-26 05:23:27 -05:00
case history . Join :
2019-05-06 23:17:57 -04:00
if eventPlayback {
if extendedJoin {
2021-03-17 14:36:52 -04: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-06 23:17:57 -04:00
} else {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "JOIN" , chname )
2019-05-06 23:17:57 -04:00
}
2018-11-26 05:23:27 -05:00
} else {
2019-05-19 04:27:44 -04:00
if ! playJoinsAsPrivmsg {
2019-05-06 23:17:57 -04: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 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2018-11-26 05:23:27 -05:00
}
case history . Part :
2019-05-06 23:17:57 -04:00
if eventPlayback {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "PART" , chname , item . Message . Message )
2019-05-06 23:17:57 -04:00
} else {
2019-05-19 04:27:44 -04:00
if ! playJoinsAsPrivmsg {
2019-05-06 23:17:57 -04:00
continue // #474
}
message := fmt . Sprintf ( client . t ( "%[1]s left the channel (%[2]s)" ) , nick , item . Message . Message )
2021-11-01 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-06 23:17:57 -04:00
}
2018-11-26 05:23:27 -05:00
case history . Kick :
2019-05-06 23:17:57 -04:00
if eventPlayback {
2021-03-17 14:36:52 -04: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-06 23:17:57 -04:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s kicked %[2]s (%[3]s)" ) , nick , item . Params [ 0 ] , item . Message . Message )
2021-11-01 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-06 23:17:57 -04:00
}
case history . Quit :
if eventPlayback {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "QUIT" , item . Message . Message )
2019-05-06 23:17:57 -04:00
} else {
2019-05-19 04:27:44 -04:00
if ! playJoinsAsPrivmsg {
2019-05-06 23:17:57 -04:00
continue // #474
}
message := fmt . Sprintf ( client . t ( "%[1]s quit (%[2]s)" ) , nick , item . Message . Message )
2021-11-01 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-06 23:17:57 -04:00
}
case history . Nick :
if eventPlayback {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "NICK" , item . Params [ 0 ] )
2019-05-06 23:17:57 -04:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s changed nick to %[2]s" ) , nick , item . Params [ 0 ] )
2021-11-01 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-06 23:17:57 -04:00
}
2020-04-22 22:51:19 -04:00
case history . Topic :
if eventPlayback {
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "TOPIC" , chname , item . Message . Message )
2020-04-22 22:51:19 -04:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s set the channel topic to: %[2]s" ) , nick , item . Message . Message )
2021-11-01 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2020-04-22 22:51:19 -04: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 14:36:52 -04:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "MODE" , params ... )
2020-04-22 22:51:19 -04:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s set channel modes: %[2]s" ) , nick , strings . Join ( params [ 1 : ] , " " ) )
2021-11-01 01:24:14 -04:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2020-04-22 22:51:19 -04:00
}
2018-11-26 05:23:27 -05:00
}
}
}
2017-10-22 19:50:16 -04:00
// SendTopic sends the channel topic to the given client.
2019-02-13 13:22:00 -05: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-22 19:50:16 -04:00
channel . stateMutex . RLock ( )
name := channel . name
topic := channel . topic
topicSetBy := channel . topicSetBy
topicSetTime := channel . topicSetTime
2019-04-12 00:08:46 -04:00
_ , hasClient := channel . members [ client ]
2017-10-22 19:50:16 -04:00
channel . stateMutex . RUnlock ( )
2019-04-12 00:08:46 -04: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-22 19:50:16 -04:00
if topic == "" {
2019-02-13 13:22:00 -05:00
if sendNoTopic {
rb . Add ( nil , client . server . name , RPL_NOTOPIC , client . nick , name , client . t ( "No topic is set" ) )
}
2012-12-08 22:54:58 -08:00
return
}
2018-02-06 00:21:08 +10: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-16 22:20:42 -08:00
}
2017-03-27 14:29:51 +10:00
// SetTopic sets the topic of this channel, if the client is allowed to do so.
2018-02-06 00:21:08 +10:00
func ( channel * Channel ) SetTopic ( client * Client , topic string , rb * ResponseBuffer ) {
2021-02-06 22:45:34 -05:00
if ! channel . hasClient ( client ) {
2019-03-19 03:35:49 -04:00
rb . Add ( nil , client . server . name , ERR_NOTONCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "You're not on that channel" ) )
2014-02-16 22:20:42 -08:00
return
}
2021-02-06 22:45:34 -05:00
if channel . flags . HasMode ( modes . OpOnlyTopic ) && ! ( channel . ClientIsAtLeast ( client , modes . Halfop ) || client . HasRoleCapabs ( "samode" ) ) {
2019-03-19 03:35:49 -04:00
rb . Add ( nil , client . server . name , ERR_CHANOPRIVSNEEDED , client . Nick ( ) , channel . Name ( ) , client . t ( "You're not a channel operator" ) )
2014-02-15 19:56:38 -08:00
return
}
2021-03-18 03:49:12 -04:00
topic = ircutils . TruncateUTF8Safe ( topic , client . server . Config ( ) . Limits . TopicLen )
2016-09-12 12:22:50 +10:00
2017-10-22 19:50:16 -04:00
channel . stateMutex . Lock ( )
2020-04-22 22:51:19 -04:00
chname := channel . name
2014-02-16 22:20:42 -08:00
channel . topic = topic
2017-03-24 13:44:54 +10:00
channel . topicSetBy = client . nickMaskString
2019-05-12 03:12:50 -04:00
channel . topicSetTime = time . Now ( ) . UTC ( )
2017-10-22 19:50:16 -04:00
channel . stateMutex . Unlock ( )
2014-02-20 20:47:05 -08:00
2020-04-22 22:51:19 -04:00
details := client . Details ( )
2021-03-17 14:36:52 -04:00
isBot := client . HasMode ( modes . Bot )
2020-04-22 22:51:19 -04:00
message := utils . MakeMessage ( topic )
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "TOPIC" , chname , topic )
2017-10-22 19:50:16 -04:00
for _ , member := range channel . Members ( ) {
2019-04-12 00:08:46 -04:00
for _ , session := range member . Sessions ( ) {
2020-04-22 22:51:19 -04:00
if session != rb . session {
2021-03-17 14:36:52 -04:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "TOPIC" , chname , topic )
2019-04-12 00:08:46 -04:00
}
2018-02-06 00:21:08 +10:00
}
2014-02-19 22:20:34 -08:00
}
2017-03-24 13:44:54 +10:00
2020-04-22 22:51:19 -04:00
channel . AddHistoryItem ( history . Item {
Type : history . Topic ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : message ,
2021-03-17 19:01:38 -04:00
IsBot : isBot ,
2020-05-12 12:05:40 -04:00
} , details . account )
2020-04-22 22:51:19 -04:00
2019-03-11 19:24:45 -04:00
channel . MarkDirty ( IncludeTopic )
2012-12-15 14:34:20 -08:00
}
2020-09-19 11:01:58 -07: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-22 19:50:16 -04:00
channel . stateMutex . RLock ( )
2021-01-20 21:13:18 -05:00
memberData , hasClient := channel . members [ client ]
2020-10-21 11:08:55 -04:00
channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
clientModes := memberData . modes
2017-01-11 02:09:08 +10:00
2020-10-21 11:08:55 -04:00
if ! hasClient && channel . flags . HasMode ( modes . NoOutside ) {
// TODO: enforce regular +b bans on -n channels?
2020-09-19 11:01:58 -07:00
return false , modes . NoOutside
2014-02-22 12:49:33 -08:00
}
2020-10-21 11:08:55 -04:00
if channel . isMuted ( client ) && clientModes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
return false , modes . BanMask
}
if channel . flags . HasMode ( modes . Moderated ) && clientModes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-09-19 11:01:58 -07:00
return false , modes . Moderated
2014-02-22 12:49:33 -08:00
}
2020-10-21 11:08:55 -04:00
if channel . flags . HasMode ( modes . RegisteredOnlySpeak ) && client . Account ( ) == "" &&
2021-06-18 18:26:45 -04:00
clientModes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-09-19 11:01:58 -07:00
return false , modes . RegisteredOnlySpeak
2017-03-28 17:32:03 +10:00
}
2020-09-19 11:01:58 -07:00
return true , modes . Mode ( '?' )
2014-02-22 12:49:33 -08:00
}
2020-10-21 11:08:55 -04:00
func ( channel * Channel ) isMuted ( client * Client ) bool {
muteRe := channel . lists [ modes . BanMask ] . MuteRegexp ( )
if muteRe == nil {
return false
}
2020-11-01 18:09:04 -05:00
nuh := client . NickMaskCasefolded ( )
2020-10-21 11:08:55 -04:00
return muteRe . MatchString ( nuh ) && ! channel . lists [ modes . ExceptMask ] . MatchMute ( nuh )
}
2021-03-11 01:21:03 -05: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 15:26:37 -05:00
func msgCommandToHistType ( command string ) ( history . ItemType , error ) {
2019-03-07 02:31:46 -05:00
switch command {
case "PRIVMSG" :
2019-03-19 03:35:49 -04:00
return history . Privmsg , nil
2019-03-07 02:31:46 -05:00
case "NOTICE" :
2019-03-19 03:35:49 -04:00
return history . Notice , nil
2019-03-07 02:31:46 -05:00
case "TAGMSG" :
2019-03-19 03:35:49 -04:00
return history . Tagmsg , nil
2019-03-07 02:31:46 -05:00
default :
2019-03-19 03:35:49 -04:00
return history . ItemType ( 0 ) , errInvalidParams
}
}
2019-04-23 00:05:12 -04:00
func ( channel * Channel ) SendSplitMessage ( command string , minPrefixMode modes . Mode , clientOnlyTags map [ string ] string , client * Client , message utils . SplitMessage , rb * ResponseBuffer ) {
2019-12-23 15:26:37 -05:00
histType , err := msgCommandToHistType ( command )
2019-03-19 03:35:49 -04:00
if err != nil {
2014-02-08 23:33:56 -08:00
return
}
2017-01-11 02:09:08 +10:00
2020-09-19 11:01:58 -07:00
if canSpeak , mode := channel . CanSpeak ( client ) ; ! canSpeak {
2019-03-19 03:35:49 -04:00
if histType != history . Notice {
2020-09-19 11:01:58 -07: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 03:35:49 -04:00
}
2017-01-14 15:28:50 +10:00
return
}
2020-01-26 20:50:27 -05: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 12:05:40 -04:00
details := client . Details ( )
2021-03-17 19:01:38 -04:00
isBot := client . HasMode ( modes . Bot )
2019-04-12 00:08:46 -04:00
chname := channel . Name ( )
2021-03-04 22:29:34 -05: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 00:05:12 -04: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 15:28:50 +10:00
}
2019-04-23 00:05:12 -04:00
2020-10-20 13:37:38 -04:00
if channel . flags . HasMode ( modes . OpModerated ) {
channel . stateMutex . RLock ( )
2021-01-20 21:13:18 -05:00
cuData := channel . members [ client ]
2020-10-20 13:37:38 -04:00
channel . stateMutex . RUnlock ( )
2021-01-20 21:13:18 -05:00
if cuData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-20 13:37:38 -04:00
// max(statusmsg_minmode, halfop)
if minPrefixMode == modes . Mode ( 0 ) || minPrefixMode == modes . Voice {
minPrefixMode = modes . Halfop
}
}
}
2018-02-11 08:57:15 +10:00
// send echo-message
2020-07-24 02:55:46 -04:00
rb . addEchoMessage ( clientOnlyTags , details . nickMask , details . accountName , command , chname , message )
2019-04-12 00:08:46 -04:00
2020-11-27 00:13:47 -05:00
var cache MessageCache
2021-03-17 19:01:38 -04:00
cache . InitializeSplitMessage ( channel . server , details . nickMask , details . accountName , isBot , clientOnlyTags , command , chname , message )
2019-04-12 00:08:46 -04:00
for _ , member := range channel . Members ( ) {
2019-04-23 00:05:12 -04:00
if minPrefixMode != modes . Mode ( 0 ) && ! channel . ClientIsAtLeast ( member , minPrefixMode ) {
2020-10-20 13:37:38 -04:00
// STATUSMSG or OpModerated
2019-03-07 02:31:46 -05:00
continue
2017-01-14 21:48:57 +10:00
}
2019-04-12 00:08:46 -04:00
for _ , session := range member . Sessions ( ) {
2020-07-24 02:37:36 -04:00
if session == rb . session {
continue // we already sent echo-message, if applicable
}
2020-02-18 19:38:42 -05:00
if isCTCP && session . isTor {
continue // #753
}
2020-11-27 00:13:47 -05:00
cache . Send ( session )
2017-01-14 15:28:50 +10:00
}
}
2018-11-26 05:23:27 -05:00
2020-10-20 13:37:38 -04:00
// #959: don't save STATUSMSG (or OpModerated)
2020-04-24 01:33:21 -04:00
if minPrefixMode == modes . Mode ( 0 ) {
channel . AddHistoryItem ( history . Item {
Type : histType ,
Message : message ,
2020-05-12 12:05:40 -04:00
Nick : details . nickMask ,
AccountName : details . accountName ,
2020-04-24 01:33:21 -04:00
Tags : clientOnlyTags ,
2021-03-17 19:01:38 -04:00
IsBot : isBot ,
2020-05-12 12:05:40 -04:00
} , details . account )
2020-04-24 01:33:21 -04:00
}
2017-01-14 15:28:50 +10:00
}
2020-03-25 12:08:08 -04: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 06:52:07 -05:00
if target == nil {
2020-03-25 12:08:08 -04:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . Nick ( ) , utils . SafeErrorParam ( change . Arg ) , client . t ( "No such nick" ) )
return
2014-02-22 15:01:11 -08:00
}
2020-03-25 12:08:08 -04:00
change . Arg = target . Nick ( )
2014-02-22 15:01:11 -08:00
2017-10-22 19:50:16 -04:00
channel . stateMutex . Lock ( )
2021-01-20 21:13:18 -05:00
memberData , exists := channel . members [ target ]
2017-10-22 19:50:16 -04:00
if exists {
2021-01-20 21:13:18 -05:00
if memberData . modes . SetMode ( change . Mode , change . Op == modes . Add ) {
2020-03-25 12:08:08 -04:00
applied = true
result = change
2018-04-22 18:47:10 -04:00
}
2014-02-22 15:01:11 -08:00
}
2017-10-22 19:50:16 -04:00
channel . stateMutex . Unlock ( )
2014-02-22 15:01:11 -08:00
2017-10-22 19:50:16 -04:00
if ! exists {
2019-02-17 06:51:48 -05:00
rb . Add ( nil , client . server . name , ERR_USERNOTINCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "They aren't on that channel" ) )
2014-02-22 15:01:11 -08:00
}
2020-12-02 03:56:00 -05:00
if applied {
target . markDirty ( IncludeChannels )
}
2018-04-22 18:47:10 -04:00
return
2014-02-22 15:01:11 -08:00
}
2017-03-27 14:29:51 +10:00
// ShowMaskList shows the given list to the client.
2018-02-06 00:21:08 +10:00
func ( channel * Channel ) ShowMaskList ( client * Client , mode modes . Mode , rb * ResponseBuffer ) {
2017-03-26 20:37:13 +10:00
// choose appropriate modes
var rpllist , rplendoflist string
2018-02-03 20:21:32 +10:00
if mode == modes . BanMask {
2017-03-26 20:37:13 +10:00
rpllist = RPL_BANLIST
rplendoflist = RPL_ENDOFBANLIST
2018-02-03 20:21:32 +10:00
} else if mode == modes . ExceptMask {
2017-03-26 20:37:13 +10:00
rpllist = RPL_EXCEPTLIST
rplendoflist = RPL_ENDOFEXCEPTLIST
2018-02-03 20:21:32 +10:00
} else if mode == modes . InviteMask {
2017-03-26 20:37:13 +10:00
rpllist = RPL_INVITELIST
rplendoflist = RPL_ENDOFINVITELIST
}
2017-11-03 02:36:55 -04:00
nick := client . Nick ( )
2019-10-10 04:17:44 -04: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 20:37:13 +10:00
}
2017-10-22 19:50:16 -04:00
2019-10-10 04:17:44 -04:00
rb . Add ( nil , client . server . name , rplendoflist , nick , chname , client . t ( "End of list" ) )
2014-03-07 17:35:58 -08:00
}
2014-03-07 17:09:49 -08:00
2017-10-22 19:50:16 -04:00
// Quit removes the given client from the channel
func ( channel * Channel ) Quit ( client * Client ) {
2018-04-24 03:11:11 -04:00
channelEmpty := func ( ) bool {
channel . joinPartMutex . Lock ( )
defer channel . joinPartMutex . Unlock ( )
2017-10-22 19:50:16 -04:00
2018-04-24 03:11:11 -04:00
channel . stateMutex . Lock ( )
channel . members . Remove ( client )
channelEmpty := len ( channel . members ) == 0
channel . stateMutex . Unlock ( )
channel . regenerateMembersCache ( )
return channelEmpty
} ( )
2018-03-01 10:37:30 -05:00
2018-04-24 03:11:11 -04:00
if channelEmpty {
2018-03-01 10:37:30 -05:00
client . server . channels . Cleanup ( channel )
}
2018-04-24 03:11:11 -04:00
client . removeChannel ( channel )
2014-02-16 23:29:11 -08:00
}
2019-12-16 19:50:15 -05: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-16 23:29:11 -08:00
}
2017-10-22 19:50:16 -04:00
if ! channel . hasClient ( target ) {
2019-03-19 03:35:49 -04:00
rb . Add ( nil , client . server . name , ERR_USERNOTINCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "They aren't on that channel" ) )
2014-02-16 23:29:11 -08:00
return
}
2021-03-18 03:49:12 -04:00
comment = ircutils . TruncateUTF8Safe ( comment , channel . server . Config ( ) . Limits . KickLen )
2016-09-12 12:22:50 +10:00
2020-01-18 23:47:05 -05:00
message := utils . MakeMessage ( comment )
2020-05-12 12:05:40 -04:00
details := client . Details ( )
2021-03-17 14:36:52 -04:00
isBot := client . HasMode ( modes . Bot )
2019-05-06 23:17:57 -04:00
2017-11-03 02:36:55 -04:00
targetNick := target . Nick ( )
2019-04-12 00:08:46 -04:00
chname := channel . Name ( )
2017-10-22 19:50:16 -04:00
for _ , member := range channel . Members ( ) {
2019-04-12 00:08:46 -04:00
for _ , session := range member . Sessions ( ) {
if session != rb . session {
2021-03-17 14:36:52 -04:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "KICK" , chname , targetNick , comment )
2019-04-12 00:08:46 -04:00
}
}
2014-02-19 22:20:34 -08:00
}
2021-03-17 14:36:52 -04:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "KICK" , chname , targetNick , comment )
2017-10-22 19:50:16 -04:00
2019-05-06 23:17:57 -04:00
histItem := history . Item {
2018-11-26 05:23:27 -05:00
Type : history . Kick ,
2020-05-12 12:05:40 -04:00
Nick : details . nickMask ,
AccountName : details . accountName ,
2019-03-07 02:31:46 -05:00
Message : message ,
2021-03-17 14:36:52 -04:00
IsBot : isBot ,
2019-05-06 23:17:57 -04:00
}
histItem . Params [ 0 ] = targetNick
2020-05-12 12:05:40 -04:00
channel . AddHistoryItem ( histItem , details . account )
2018-11-26 05:23:27 -05:00
2017-10-22 19:50:16 -04:00
channel . Quit ( target )
2014-02-16 17:23:47 -08:00
}
2014-02-25 07:28:09 -08:00
2021-02-04 15:26:03 -05: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
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 14:36:52 -04: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 15:26:03 -05:00
}
member . removeChannel ( channel )
}
}
2017-03-27 14:29:51 +10:00
// Invite invites the given client to the channel, if the inviter can do so.
2018-02-06 00:21:08 +10:00
func ( channel * Channel ) Invite ( invitee * Client , inviter * Client , rb * ResponseBuffer ) {
2020-10-25 20:40:41 -04: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 07:28:09 -08:00
return
}
2020-10-25 20:40:41 -04:00
inviteOnly := channel . flags . HasMode ( modes . InviteOnly )
2021-12-19 18:30:18 -05:00
hasPrivs := channel . ClientIsAtLeast ( inviter , modes . ChannelOperator )
if inviteOnly && ! hasPrivs {
2020-10-25 20:40:41 -04:00
rb . Add ( nil , inviter . server . name , ERR_CHANOPRIVSNEEDED , inviter . Nick ( ) , chname , inviter . t ( "You're not a channel operator" ) )
2020-03-18 06:13:57 -04:00
return
}
2020-10-25 20:40:41 -04:00
if inviteePresent {
2020-03-18 06:13:57 -04: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 07:28:09 -08:00
return
}
2021-12-19 18:30:18 -05: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-25 20:40:41 -04:00
invitee . Invite ( chcfname , createdAt )
}
2014-03-07 17:09:49 -08:00
2020-11-29 22:12:06 -05:00
details := inviter . Details ( )
2021-03-17 14:36:52 -04:00
isBot := inviter . HasMode ( modes . Bot )
2020-11-29 22:12:06 -05:00
tDetails := invitee . Details ( )
tnick := invitee . Nick ( )
2020-12-14 08:24:38 -05:00
message := utils . MakeMessage ( chname )
2020-11-29 22:12:06 -05:00
item := history . Item {
Type : history . Invite ,
Message : message ,
}
2017-10-22 19:50:16 -04:00
for _ , member := range channel . Members ( ) {
2019-04-12 00:08:46 -04: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 14:36:52 -04:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "INVITE" , tnick , chname )
2019-04-12 00:08:46 -04:00
}
2016-10-16 14:14:55 +10:00
}
}
2020-11-29 22:12:06 -05:00
rb . Add ( nil , inviter . server . name , RPL_INVITING , details . nick , tnick , chname )
2021-03-17 14:36:52 -04: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 01:55:13 -04:00
if away , awayMessage := invitee . Away ( ) ; away {
2020-11-29 22:12:06 -05:00
rb . Add ( nil , inviter . server . name , RPL_AWAY , details . nick , tnick , awayMessage )
2014-02-25 07:28:09 -08:00
}
2020-11-29 22:12:06 -05:00
inviter . addHistoryItem ( invitee , item , & details , & tDetails , channel . server . Config ( ) )
2014-02-25 07:28:09 -08:00
}
2020-06-29 15:41:29 -04:00
2020-10-25 22:16:19 -04: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-06 21:01:44 -05:00
rb . Add ( nil , channel . server . name , "FAIL" , "UNINVITE" , "PRIVS_NEEDED" , channel . Name ( ) , inviter . t ( "You're not a channel operator" ) )
2020-10-25 22:16:19 -04:00
return
}
invitee . Uninvite ( channel . NameCasefolded ( ) )
rb . Add ( nil , channel . server . name , "UNINVITE" , invitee . Nick ( ) , channel . Name ( ) )
}
2020-10-02 08:13:52 -04: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-20 21:13:18 -05:00
clientData , found := channel . members [ client ]
if ! found {
2020-10-02 08:13:52 -04:00
return // non-members have no friends
}
if ! channel . flags . HasMode ( modes . Auditorium ) {
return channel . membersCache // default behavior for members
}
2021-01-20 21:13:18 -05:00
if clientData . modes . HighestChannelUserMode ( ) != modes . Mode ( 0 ) {
2020-10-02 08:13:52 -04: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-20 21:13:18 -05:00
for member , memberData := range channel . members {
if memberData . modes . HighestChannelUserMode ( ) != modes . Mode ( 0 ) {
2020-10-02 08:13:52 -04:00
friends = append ( friends , member )
}
}
return
}
2020-06-29 15:41:29 -04: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
}