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"
2017-09-29 04:07:52 +02:00
"github.com/oragono/oragono/irc/caps"
2018-11-26 11:23:27 +01:00
"github.com/oragono/oragono/irc/history"
2018-02-03 11:21:32 +01:00
"github.com/oragono/oragono/irc/modes"
2018-11-26 11:23:27 +01:00
"github.com/oragono/oragono/irc/utils"
2017-11-09 04:19:50 +01:00
)
2020-02-19 01:38:42 +01:00
type ChannelSettings struct {
History HistoryStatus
}
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
members MemberSet
2018-04-24 11:46:01 +02:00
membersCache [ ] * Client // allow iteration over channel members without holding the lock
2017-11-07 20:38:18 +01:00
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
2019-04-15 17:13:13 +02: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-12 00:24:45 +01:00
dirtyBits uint
2020-02-19 01:38:42 +01:00
settings ChannelSettings
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.
2019-12-17 19:21:26 +01:00
func NewChannel ( s * Server , name , casefoldedName string , registered bool ) * 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 {
2020-06-12 23:13:47 +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 ( )
2019-03-12 00:24:45 +01:00
channel . writerSemaphore . Initialize ( 1 )
2020-02-19 01:38:42 +01:00
channel . history . Initialize ( 0 , 0 )
2019-03-12 00:24:45 +01:00
if ! registered {
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
}
2019-03-12 00:24:45 +01: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 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 )
}
2019-03-12 00:24:45 +01: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-19 01:38:42 +01:00
func ( channel * Channel ) resizeHistory ( config * Config ) {
2020-02-24 20:09:00 +01:00
status , _ := channel . historyStatus ( config )
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 ( )
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
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
2018-04-04 03:49:40 +02:00
func ( channel * Channel ) ExportRegistration ( includeFlags uint ) ( info RegisteredChannel ) {
2017-11-09 04:19:50 +01:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
info . Name = channel . name
2019-03-12 00:24:45 +01:00
info . NameCasefolded = channel . nameCasefolded
2017-11-09 04:19:50 +01:00
info . Founder = channel . registeredFounder
info . RegisteredAt = channel . registeredTime
2018-04-04 03:49:40 +02: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
2018-04-23 00:47:10 +02:00
info . Modes = channel . flags . AllModes ( )
2020-01-08 08:14:41 +01:00
info . UserLimit = channel . userLimit
2018-04-04 03:49:40 +02:00
}
if includeFlags & IncludeLists != 0 {
2019-10-10 10:17:44 +02:00
info . Bans = channel . lists [ modes . BanMask ] . Masks ( )
info . Invites = channel . lists [ modes . InviteMask ] . Masks ( )
info . Excepts = channel . lists [ modes . ExceptMask ] . Masks ( )
2018-04-04 03:49:40 +02:00
info . AccountToUMode = make ( map [ string ] modes . Mode )
for account , mode := range channel . accountToUMode {
info . AccountToUMode [ account ] = mode
}
2017-11-09 04:19:50 +01:00
}
2020-02-19 01:38:42 +01:00
if includeFlags & IncludeSettings != 0 {
info . Settings = channel . settings
}
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 {
2020-03-19 12:26:17 +01:00
config := channel . server . Config ( )
2019-03-12 00:24:45 +01:00
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 12:26:17 +01:00
if len ( channel . members ) != 0 {
return false
}
if channel . registeredFounder == "" {
return true
}
// a registered channel must be fully written to the DB,
// and not set to ephemeral history (#704)
return channel . dirtyBits == 0 &&
channelHistoryStatus ( config , true , channel . settings . History ) != HistoryEphemeral
2019-03-12 00:24:45 +01: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-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 ) {
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 )
}
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 ( )
2017-10-23 01:50:16 +02:00
result := make ( [ ] * Client , len ( channel . members ) )
i := 0
for client := range channel . members {
result [ i ] = client
i ++
}
2018-04-24 09:11:11 +02:00
channel . stateMutex . RUnlock ( )
channel . stateMutex . Lock ( )
2017-10-23 01:50:16 +02:00
channel . membersCache = result
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 ( )
clientModes , isJoined := channel . members [ client ]
channel . stateMutex . RUnlock ( )
2019-04-29 03:09:56 +02:00
isOper := client . HasMode ( modes . Operator )
2020-10-02 14:13:52 +02:00
respectAuditorium := channel . flags . HasMode ( modes . Auditorium ) && ! isOper &&
( ! isJoined || clientModes . 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
maxNamLen := 480 - len ( client . server . name ) - len ( client . Nick ( ) )
var namesLines [ ] string
2020-06-09 05:38:10 +02:00
var buffer strings . Builder
2019-04-29 03:09:56 +02:00
if isJoined || ! channel . flags . HasMode ( modes . Secret ) || isOper {
2019-04-23 07:52:26 +02:00
for _ , target := range channel . Members ( ) {
var nick string
if isUserhostInNames {
nick = target . NickMaskString ( )
} else {
nick = target . Nick ( )
}
channel . stateMutex . RLock ( )
modeSet := channel . members [ target ]
channel . stateMutex . RUnlock ( )
if modeSet == nil {
continue
}
2020-02-19 01:38:42 +01:00
if ! isJoined && target . HasMode ( modes . Invisible ) && ! isOper {
2019-04-23 07:52:26 +02:00
continue
}
2020-10-02 14:13:52 +02:00
if respectAuditorium && modeSet . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
continue
}
2019-04-23 07:52:26 +02: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 11:46:01 +02:00
}
if buffer . Len ( ) > 0 {
2019-04-23 07:52:26 +02:00
namesLines = append ( namesLines , buffer . String ( ) )
2018-04-24 11:46:01 +02:00
}
2016-06-19 06:55:24 +02:00
}
2018-04-24 11:46:01 +02:00
for _ , line := range namesLines {
if buffer . Len ( ) > 0 {
rb . Add ( nil , client . server . name , RPL_NAMREPLY , client . nick , "=" , channel . name , line )
}
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , RPL_ENDOFNAMES , client . nick , channel . name , 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 ( )
clientModes := 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
}
2018-05-23 21:35:50 +02:00
for _ , mode := range modes . ChannelUserModes {
2018-04-23 00:47:10 +02:00
if clientModes . 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 ( )
modes , present := channel . members [ client ]
if ! present {
return ""
} else {
return modes . Prefixes ( isMultiPrefix )
}
}
2020-06-15 20:16:02 +02:00
func ( channel * Channel ) ClientStatus ( client * Client ) ( present bool , cModes modes . Modes ) {
2020-04-15 10:14:17 +02:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
modes , present := channel . members [ client ]
2020-06-15 20:16:02 +02:00
return present , modes . AllModes ( )
2020-04-15 10:14:17 +02: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
2017-10-23 01:50:16 +02:00
clientModes := channel . members [ client ]
targetModes := 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
}
2019-04-23 06:05:12 +02:00
return channelUserModeHasPrivsOver ( clientModes . HighestChannelUserMode ( ) , targetModes . 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 ) {
2020-05-17 20:02:59 +02:00
hasPrivs := client . HasMode ( modes . Operator )
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
isMember := hasPrivs || channel . members [ client ] != nil
2014-02-22 21:49:33 +01:00
showKey := isMember && ( channel . key != "" )
showUserLimit := channel . userLimit > 0
2018-02-03 11:21:32 +01:00
mods := "+"
2017-10-23 01:50:16 +02:00
2014-02-22 21:49:33 +01:00
// flags with args
if showKey {
2018-02-03 11:21:32 +01:00
mods += modes . Key . String ( )
2014-02-09 08:33:56 +01:00
}
2014-02-22 21:49:33 +01:00
if showUserLimit {
2018-02-03 11:21:32 +01:00
mods += modes . UserLimit . String ( )
2014-02-22 21:49:33 +01:00
}
2014-02-15 06:57:08 +01:00
2018-04-23 00:47:10 +02:00
mods += channel . flags . String ( )
2018-02-03 11:21:32 +01:00
result = [ ] string { mods }
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
}
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
2020-02-24 20:09:00 +01:00
func ( channel * Channel ) historyStatus ( config * Config ) ( status HistoryStatus , target string ) {
if ! config . History . Enabled {
return HistoryDisabled , ""
2020-02-19 01:38:42 +01:00
}
channel . stateMutex . RLock ( )
target = channel . nameCasefolded
historyStatus := channel . settings . History
registered := channel . registeredFounder != ""
channel . stateMutex . RUnlock ( )
2020-03-19 12:26:17 +01:00
return channelHistoryStatus ( config , registered , historyStatus ) , target
}
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
}
2020-02-24 20:09:00 +01:00
status , target := channel . historyStatus ( channel . server . Config ( ) )
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-07-01 01:24:56 +02:00
func ( channel * Channel ) Join ( client * Client , key string , isSajoin bool , rb * ResponseBuffer ) error {
2019-01-01 19:15:38 +01:00
details := client . Details ( )
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 ]
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-07-01 01:24:56 +02: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 {
return errLimitExceeded
}
2018-04-24 09:11:11 +02:00
2020-07-01 01:24:56 +02:00
if chkey != "" && ! utils . SecretTokensMatch ( chkey , key ) {
return errWrongChannelKey
}
2014-02-22 21:49:33 +01:00
2020-07-01 01:24:56 +02:00
if channel . flags . HasMode ( modes . InviteOnly ) &&
! channel . lists [ modes . InviteMask ] . Match ( details . nickMaskCasefolded ) {
return errInviteOnly
}
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 ) {
return errBanned
}
2014-03-08 02:09:49 +01:00
2020-07-08 11:32:14 +02:00
if details . account == "" &&
2020-07-09 10:33:09 +02:00
( channel . flags . HasMode ( modes . RegisteredOnly ) || channel . server . Defcon ( ) <= 2 ) {
2020-07-01 01:24:56 +02:00
return errRegisteredOnly
}
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 {
return joinErr
2019-05-12 10:49:45 +02:00
}
2019-01-01 19:15:38 +01:00
client . server . logger . Debug ( "join" , 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 {
channel . members [ client ] . 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 )
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
message = utils . MakeMessage ( "" )
histItem := history . Item {
Type : history . Join ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : message ,
}
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-07-01 01:24:56 +02: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-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 ( )
memberModes , ok := channel . members [ member ]
channel . stateMutex . RUnlock ( )
if ! ok || memberModes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
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 ) {
2019-05-07 05:17:57 +02:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , nil , "JOIN" , chname , details . accountName , details . realname )
2019-04-12 06:08:46 +02:00
} else {
2019-05-07 05:17:57 +02:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , nil , "JOIN" , chname )
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 ) {
2020-09-01 08:41:15 +02:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . accountName , 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 ) {
2019-05-07 05:17:57 +02:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , nil , "JOIN" , chname , details . accountName , details . realname )
2016-08-14 03:59:33 +02:00
} else {
2019-05-07 05:17:57 +02:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , nil , "JOIN" , chname )
2016-08-14 03:59:33 +02:00
}
2018-04-24 09:11:11 +02:00
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-07-01 01:24:56 +02: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 {
2020-02-19 01:38:42 +01:00
_ , seq , _ := channel . server . GetHistorySequence ( channel , client , "" )
if seq != nil {
zncMax := channel . server . Config ( ) . History . ZNCMax
2020-02-28 01:07:49 +01: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 {
2020-02-19 01:38:42 +01:00
_ , seq , _ := channel . server . GetHistorySequence ( channel , client , "" )
if seq != nil {
items , _ , _ = seq . Between ( history . Selector { } , history . Selector { } , replayLimit )
}
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 {
channel . replayHistoryItems ( rb , items , true )
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 ( )
2019-04-12 06:08:46 +02:00
if session . capabilities . Has ( caps . ExtendedJoin ) {
2019-05-22 22:10:56 +02:00
sessionRb . Add ( nil , details . nickMask , "JOIN" , channel . Name ( ) , details . accountName , details . realname )
2019-04-12 06:08:46 +02:00
} else {
2019-05-22 22:10:56 +02:00
sessionRb . Add ( nil , details . nickMask , "JOIN" , channel . Name ( ) )
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
clientModes , ok := channel . members [ client ]
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 ( )
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 ) &&
clientModes . HighestChannelUserMode ( ) == modes . Mode ( 0 )
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 ( )
memberModes , ok := channel . members [ member ]
channel . stateMutex . RUnlock ( )
if ! ok || memberModes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
continue
}
}
2019-12-03 03:13:09 +01:00
member . sendFromClientInternal ( false , splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , nil , "PART" , params ... )
2014-02-20 07:20:34 +01:00
}
2019-12-03 03:13:09 +01:00
rb . AddFromClient ( splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , nil , "PART" , params ... )
2019-04-12 06:08:46 +02:00
for _ , session := range client . Sessions ( ) {
if session != rb . session {
2019-12-03 03:13:09 +01:00
session . sendFromClientInternal ( false , splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , 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 ,
} , details . account )
}
2018-11-26 11:23:27 +01:00
2019-01-01 19:15:38 +01:00
client . server . logger . Debug ( "part" , fmt . Sprintf ( "%s left channel %s" , details . nick , chname ) )
2012-12-09 07:54:58 +01:00
}
2018-11-26 11:23:27 +01:00
// Resume is called after a successful global resume to:
// 1. Replace the old client with the new in the channel's data structures
// 2. Send JOIN and MODE lines to channel participants (including the new client)
// 3. Replay missed message history to the client
2019-05-22 03:40:25 +02:00
func ( channel * Channel ) Resume ( session * Session , timestamp time . Time ) {
channel . resumeAndAnnounce ( session )
2018-11-26 11:23:27 +01:00
if ! timestamp . IsZero ( ) {
2020-02-19 01:38:42 +01:00
channel . replayHistoryForResume ( session , timestamp , time . Time { } )
2018-11-26 11:23:27 +01:00
}
}
2019-05-22 03:40:25 +02:00
func ( channel * Channel ) resumeAndAnnounce ( session * Session ) {
channel . stateMutex . RLock ( )
modeSet := channel . members [ session . client ]
channel . stateMutex . RUnlock ( )
if modeSet == nil {
return
}
oldModes := modeSet . String ( )
2018-11-26 11:23:27 +01:00
if 0 < len ( oldModes ) {
oldModes = "+" + oldModes
}
// send join for old clients
2019-05-22 03:40:25 +02:00
chname := channel . Name ( )
details := session . client . Details ( )
2020-10-02 14:13:52 +02:00
// TODO: for now, skip this entirely for auditoriums,
// but really we should send it to voiced clients
if ! channel . flags . HasMode ( modes . Auditorium ) {
for _ , member := range channel . Members ( ) {
for _ , session := range member . Sessions ( ) {
if session . capabilities . Has ( caps . Resume ) {
continue
}
2018-11-26 11:23:27 +01:00
2020-10-02 14:13:52 +02:00
if session . capabilities . Has ( caps . ExtendedJoin ) {
session . Send ( nil , details . nickMask , "JOIN" , chname , details . accountName , details . realname )
} else {
session . Send ( nil , details . nickMask , "JOIN" , chname )
}
2018-11-26 11:23:27 +01:00
2020-10-02 14:13:52 +02:00
if 0 < len ( oldModes ) {
session . Send ( nil , channel . server . name , "MODE" , chname , oldModes , details . nick )
}
2019-04-12 06:08:46 +02:00
}
2018-11-26 11:23:27 +01:00
}
}
2019-05-22 03:40:25 +02:00
rb := NewResponseBuffer ( session )
2018-11-26 11:23:27 +01:00
// use blocking i/o to synchronize with the later history replay
2019-04-12 06:08:46 +02:00
if rb . session . capabilities . Has ( caps . ExtendedJoin ) {
2019-05-22 22:10:56 +02:00
rb . Add ( nil , details . nickMask , "JOIN" , channel . name , details . accountName , details . realname )
2018-11-26 11:23:27 +01:00
} else {
2019-05-22 03:40:25 +02:00
rb . Add ( nil , details . nickMask , "JOIN" , channel . name )
2018-11-26 11:23:27 +01:00
}
2019-05-22 03:40:25 +02:00
channel . SendTopic ( session . client , rb , false )
channel . Names ( session . client , rb )
2018-12-28 19:45:55 +01:00
rb . Send ( true )
}
2019-05-22 03:40:25 +02:00
func ( channel * Channel ) replayHistoryForResume ( session * Session , after time . Time , before time . Time ) {
2020-02-19 01:38:42 +01:00
var items [ ] history . Item
var complete bool
afterS , beforeS := history . Selector { Time : after } , history . Selector { Time : before }
_ , seq , _ := channel . server . GetHistorySequence ( channel , session . client , "" )
if seq != nil {
items , complete , _ = seq . Between ( afterS , beforeS , channel . server . Config ( ) . History . ZNCMax )
}
2019-05-22 03:40:25 +02:00
rb := NewResponseBuffer ( session )
2020-02-21 05:47:13 +01:00
if len ( items ) != 0 {
channel . replayHistoryItems ( rb , items , false )
}
2019-05-22 03:40:25 +02:00
if ! complete && ! session . resumeDetails . HistoryIncomplete {
2018-12-28 19:45:55 +01:00
// warn here if we didn't warn already
2019-05-22 03:40:25 +02:00
rb . Add ( nil , histServMask , "NOTICE" , channel . Name ( ) , session . client . t ( "Some additional message history may have been lost" ) )
2018-12-28 19:45:55 +01:00
}
rb . Send ( true )
2018-11-26 11:23:27 +01:00
}
2019-01-02 23:52:36 +01:00
func stripMaskFromNick ( nickMask string ) ( nick string ) {
index := strings . Index ( nickMask , "!" )
if index == - 1 {
2019-05-07 05:17:57 +02:00
return nickMask
2019-01-02 23:52:36 +01:00
}
return nickMask [ 0 : index ]
}
2019-05-07 05:17:57 +02:00
func ( channel * Channel ) replayHistoryItems ( rb * ResponseBuffer , items [ ] history . Item , autoreplay 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 {
switch client . AccountSettings ( ) . ReplayJoins {
case ReplayJoinsCommandsOnly :
playJoinsAsPrivmsg = ! autoreplay
case ReplayJoinsAlways :
playJoinsAsPrivmsg = true
case ReplayJoinsNever :
playJoinsAsPrivmsg = false
}
2019-05-07 05:17:57 +02:00
}
2019-12-18 23:38:14 +01:00
2019-05-07 05:17:57 +02:00
batchID := rb . StartNestedHistoryBatch ( chname )
defer rb . EndNestedBatch ( batchID )
2018-12-28 19:45:55 +01:00
2019-05-07 05:17:57 +02:00
for _ , item := range items {
nick := stripMaskFromNick ( item . Nick )
2018-11-26 11:23:27 +01:00
switch item . Type {
case history . Privmsg :
2019-05-07 05:17:57 +02:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . Tags , "PRIVMSG" , chname , item . Message )
2018-11-26 11:23:27 +01:00
case history . Notice :
2019-05-07 05:17:57 +02:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . Tags , "NOTICE" , chname , item . Message )
case history . Tagmsg :
2020-05-22 16:58:46 +02:00
if eventPlayback {
2019-05-07 05:17:57 +02:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . Tags , "TAGMSG" , chname , item . Message )
}
2018-11-26 11:23:27 +01:00
case history . Join :
2019-05-07 05:17:57 +02:00
if eventPlayback {
if extendedJoin {
2020-02-19 01:38:42 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , nil , "JOIN" , chname , item . AccountName , item . Params [ 0 ] )
2019-05-07 05:17:57 +02:00
} else {
2020-02-19 01:38:42 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , 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 )
}
2019-05-22 03:40:25 +02:00
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , 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 {
2020-02-19 01:38:42 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , 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 )
2019-05-22 03:40:25 +02:00
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , 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 {
2020-02-19 01:38:42 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , 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 )
2019-05-22 03:40:25 +02:00
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , nil , "PRIVMSG" , chname , message )
2019-05-07 05:17:57 +02:00
}
case history . Quit :
if eventPlayback {
2020-02-19 01:38:42 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , 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 )
2019-05-22 03:40:25 +02:00
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , nil , "PRIVMSG" , chname , message )
2019-05-07 05:17:57 +02:00
}
case history . Nick :
if eventPlayback {
2020-02-19 01:38:42 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , 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 ] )
2019-05-22 03:40:25 +02:00
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , 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 {
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , nil , "TOPIC" , chname , item . Message . Message )
} else {
message := fmt . Sprintf ( client . t ( "%[1]s set the channel topic to: %[2]s" ) , nick , item . Message . Message )
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , nil , "PRIVMSG" , chname , message )
}
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 {
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , nil , "MODE" , params ... )
} else {
message := fmt . Sprintf ( client . t ( "%[1]s set channel modes: %[2]s" ) , nick , strings . Join ( params [ 1 : ] , " " ) )
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histServMask , "*" , nil , "PRIVMSG" , chname , message )
}
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 ) {
2018-04-23 00:47:10 +02:00
if ! ( client . HasMode ( modes . Operator ) || 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
}
2018-04-23 00:47:10 +02:00
if channel . flags . HasMode ( modes . OpOnlyTopic ) && ! channel . ClientIsAtLeast ( client , modes . ChannelOperator ) {
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
}
2019-05-23 01:07:12 +02:00
topicLimit := client . server . Config ( ) . Limits . TopicLen
2018-07-16 09:46:40 +02:00
if len ( topic ) > topicLimit {
topic = topic [ : topicLimit ]
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 ( )
message := utils . MakeMessage ( topic )
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , 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 {
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , 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 ,
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 ( )
2020-10-21 17:08:55 +02:00
clientModes , hasClient := channel . members [ client ]
channel . stateMutex . RUnlock ( )
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
}
2020-10-21 17:08:55 +02: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 20:01:58 +02:00
return false , modes . Moderated
2014-02-22 21:49:33 +01:00
}
2018-04-23 00:47:10 +02:00
if channel . flags . HasMode ( modes . RegisteredOnly ) && client . Account ( ) == "" {
2020-09-19 20:01:58 +02:00
return false , modes . RegisteredOnly
}
2020-10-21 17:08:55 +02:00
if channel . flags . HasMode ( modes . RegisteredOnlySpeak ) && client . Account ( ) == "" &&
clientModes . HighestChannelUserMode ( ) != 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 )
}
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 ( )
2019-04-12 06:08:46 +02:00
chname := channel . Name ( )
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 ( )
cuModes := channel . members [ client ]
channel . stateMutex . RUnlock ( )
if cuModes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
// 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
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
}
2019-04-12 06:08:46 +02:00
var tagsToUse map [ string ] string
if session . capabilities . Has ( caps . MessageTags ) {
tagsToUse = clientOnlyTags
} else if histType == history . Tagmsg {
continue
}
if histType == history . Tagmsg {
2020-05-12 18:05:40 +02:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , tagsToUse , command , chname )
2019-04-12 06:08:46 +02:00
} else {
2020-05-12 18:05:40 +02:00
session . sendSplitMsgFromClientInternal ( false , details . nickMask , details . accountName , tagsToUse , command , chname , message )
2019-04-12 06:08:46 +02:00
}
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 ,
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 ( )
modeset , exists := channel . members [ target ]
if exists {
2020-03-25 17:08:08 +01:00
if modeset . SetMode ( change . Mode , change . Op == modes . Add ) {
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
}
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 ! ( client . HasMode ( modes . Operator ) || channel . hasClient ( client ) ) {
rb . Add ( nil , client . server . name , ERR_NOTONCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "You're not on that channel" ) )
return
}
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
}
2019-12-17 01:50:15 +01:00
kicklimit := channel . server . Config ( ) . Limits . KickLen
2017-10-23 01:50:16 +02:00
if len ( comment ) > kicklimit {
comment = comment [ : kicklimit ]
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 ( )
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 {
2020-05-12 18:05:40 +02:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , nil , "KICK" , chname , targetNick , comment )
2019-04-12 06:08:46 +02:00
}
}
2014-02-20 07:20:34 +01:00
}
2020-05-12 18:05:40 +02:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , 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 ,
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
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 )
if inviteOnly && ! channel . ClientIsAtLeast ( inviter , modes . ChannelOperator ) {
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
}
2020-10-26 01:40:41 +01:00
if inviteOnly {
invitee . Invite ( chcfname , createdAt )
}
2014-03-08 02:09:49 +01:00
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 ) {
session . Send ( nil , inviter . NickMaskString ( ) , "INVITE" , invitee . Nick ( ) , chname )
}
2016-10-16 06:14:55 +02:00
}
}
2019-02-17 20:29:04 +01:00
cnick := inviter . Nick ( )
tnick := invitee . Nick ( )
rb . Add ( nil , inviter . server . name , RPL_INVITING , cnick , tnick , chname )
invitee . Send ( nil , inviter . NickMaskString ( ) , "INVITE" , tnick , chname )
2020-07-17 07:55:13 +02:00
if away , awayMessage := invitee . Away ( ) ; away {
rb . Add ( nil , inviter . server . name , RPL_AWAY , cnick , tnick , awayMessage )
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 ) {
rb . Add ( nil , channel . server . name , "FAIL" , "UNINVITE" , "NOT_PRIVED" , channel . Name ( ) , inviter . t ( "You're not a channel operator" ) )
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 ( )
clientModes := channel . members [ client ]
if clientModes == nil {
return // non-members have no friends
}
if ! channel . flags . HasMode ( modes . Auditorium ) {
return channel . membersCache // default behavior for members
}
if clientModes . HighestChannelUserMode ( ) != modes . Mode ( 0 ) {
return channel . membersCache // +v and up can see everyone in the auditorium
}
// without +v, your friends are those with +v and up
for member , memberModes := range channel . members {
if memberModes . HighestChannelUserMode ( ) != modes . Mode ( 0 ) {
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
}