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-04-07 20:44:59 +02:00
package irc
import (
2012-04-18 07:11:35 +02:00
"fmt"
2012-04-07 20:44:59 +02:00
"net"
2016-10-16 12:35:50 +02:00
"runtime/debug"
2016-06-30 07:35:34 +02:00
"strconv"
2017-01-20 14:51:36 +01:00
"strings"
2017-04-18 14:26:01 +02:00
"sync"
2017-10-23 01:50:16 +02:00
"sync/atomic"
2012-12-12 08:12:35 +01:00
"time"
2016-06-17 14:17:42 +02:00
2017-06-15 18:14:19 +02:00
"github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmsg"
2017-06-14 20:00:53 +02:00
ident "github.com/oragono/go-ident"
2017-09-29 04:07:52 +02:00
"github.com/oragono/oragono/irc/caps"
2019-01-01 22:45:37 +01:00
"github.com/oragono/oragono/irc/connection_limits"
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"
2017-06-14 20:00:53 +02:00
"github.com/oragono/oragono/irc/sno"
2017-10-05 15:47:43 +02:00
"github.com/oragono/oragono/irc/utils"
2012-04-07 20:44:59 +02:00
)
2014-03-13 01:52:25 +01:00
const (
2017-06-19 22:53:16 +02:00
// IdentTimeoutSeconds is how many seconds before our ident (username) check times out.
2018-11-26 11:23:27 +01:00
IdentTimeoutSeconds = 1.5
2018-12-30 20:55:11 +01:00
IRCv3TimestampFormat = "2006-01-02T15:04:05.000Z"
2016-06-30 07:35:34 +02:00
)
2018-11-26 11:23:27 +01:00
// ResumeDetails is a place to stash data at various stages of
// the resume process: when handling the RESUME command itself,
// when completing the registration, and when rejoining channels.
type ResumeDetails struct {
OldClient * Client
PresentedToken string
Timestamp time . Time
ResumedAt time . Time
Channels [ ] string
HistoryIncomplete bool
}
2016-10-23 03:48:57 +02:00
// Client is an IRC client.
2012-04-07 20:44:59 +02:00
type Client struct {
2018-02-11 11:30:40 +01:00
account string
2019-01-01 19:00:16 +01:00
accountName string // display name of the account: uncasefolded, '*' if not logged in
2016-10-11 15:51:46 +02:00
atime time . Time
2019-04-28 21:10:03 +02:00
away bool
2016-10-11 15:51:46 +02:00
awayMessage string
certfp string
channels ChannelSet
ctime time . Time
2017-06-11 18:01:39 +02:00
exitedSnomaskSent bool
2019-03-12 00:24:45 +01:00
flags modes . ModeSet
2016-10-11 15:51:46 +02:00
hostname string
2018-12-23 19:25:02 +01:00
invitedTo map [ string ] bool
2019-02-26 03:50:43 +01:00
isTor bool
2018-01-21 07:49:17 +01:00
languages [ ] string
2019-01-01 22:45:37 +01:00
loginThrottle connection_limits . GenericThrottle
2016-10-11 15:51:46 +02:00
nick string
nickCasefolded string
nickMaskCasefolded string
2017-04-18 14:26:01 +02:00
nickMaskString string // cache for nickmask string since it's used with lots of replies
2019-03-08 09:12:21 +01:00
nickTimer NickTimer
2018-04-19 08:48:19 +02:00
oper * Oper
2018-02-27 03:44:03 +01:00
preregNick string
2018-02-01 21:53:49 +01:00
proxiedIP net . IP // actual remote IP if using the PROXY protocol
2017-04-18 14:26:01 +02:00
rawHostname string
2016-10-11 15:51:46 +02:00
realname string
2019-02-05 06:19:03 +01:00
realIP net . IP
2016-10-11 15:51:46 +02:00
registered bool
2018-01-21 02:23:33 +01:00
resumeDetails * ResumeDetails
2019-02-12 06:27:57 +01:00
resumeID string
2016-10-11 15:51:46 +02:00
saslInProgress bool
saslMechanism string
saslValue string
2019-02-05 06:19:03 +01:00
sentPassCommand bool
2016-10-11 15:51:46 +02:00
server * Server
2019-01-31 00:59:49 +01:00
skeleton string
2019-04-12 06:08:46 +02:00
sessions [ ] * Session
2017-11-22 10:41:11 +01:00
stateMutex sync . RWMutex // tier 1
2016-10-11 15:51:46 +02:00
username string
2017-04-18 14:26:01 +02:00
vhost string
2018-11-26 11:23:27 +01:00
history * history . Buffer
2012-12-17 04:13:53 +01:00
}
2019-04-12 06:08:46 +02:00
// Session is an individual client connection to the server (TCP connection
// and associated per-connection data, such as capabilities). There is a
// many-one relationship between sessions and clients.
type Session struct {
client * Client
socket * Socket
idletimer IdleTimer
fakelag Fakelag
quitMessage string
capabilities caps . Set
maxlenRest uint32
capState caps . State
capVersion caps . Version
// TODO track per-connection real IP, proxied IP, and hostname here,
// so we can list attached sessions and their details
}
// sets the session quit message, if there isn't one already
func ( sd * Session ) SetQuitMessage ( message string ) ( set bool ) {
if message == "" {
if sd . quitMessage == "" {
sd . quitMessage = "Connection closed"
return true
} else {
return false
}
} else {
sd . quitMessage = message
return true
}
}
// set the negotiated message length based on session capabilities
func ( session * Session ) SetMaxlenRest ( ) {
maxlenRest := 512
if session . capabilities . Has ( caps . MaxLine ) {
maxlenRest = session . client . server . Config ( ) . Limits . LineLen . Rest
}
atomic . StoreUint32 ( & session . maxlenRest , uint32 ( maxlenRest ) )
}
// allow the negotiated message length limit to be read without locks; this is a convenience
// so that Session.SendRawMessage doesn't have to acquire any Client locks
func ( session * Session ) MaxlenRest ( ) int {
return int ( atomic . LoadUint32 ( & session . maxlenRest ) )
}
2019-01-01 19:00:16 +01:00
// WhoWas is the subset of client details needed to answer a WHOWAS query
type WhoWas struct {
nick string
nickCasefolded string
username string
hostname string
realname string
}
// ClientDetails is a standard set of details about a client
type ClientDetails struct {
WhoWas
nickMask string
nickMaskCasefolded string
account string
accountName string
}
2019-02-26 03:50:43 +01:00
// NewClient sets up a new client and runs its goroutine.
func RunNewClient ( server * Server , conn clientConn ) {
2014-02-14 03:59:45 +01:00
now := time . Now ( )
2018-07-16 09:46:40 +02:00
config := server . Config ( )
2019-03-07 08:31:46 +01:00
fullLineLenLimit := ircmsg . MaxlenTagsFromClient + config . Limits . LineLen . Rest
// give them 1k of grace over the limit:
socket := NewSocket ( conn . Conn , fullLineLenLimit + 1024 , config . Server . MaxSendQBytes )
2012-12-09 21:51:50 +01:00
client := & Client {
2019-04-12 06:08:46 +02:00
atime : now ,
channels : make ( ChannelSet ) ,
ctime : now ,
isTor : conn . IsTor ,
languages : server . Languages ( ) . Default ( ) ,
2019-01-01 22:45:37 +01:00
loginThrottle : connection_limits . GenericThrottle {
Duration : config . Accounts . LoginThrottling . Duration ,
Limit : config . Accounts . LoginThrottling . MaxAttempts ,
} ,
2016-09-19 14:30:29 +02:00
server : server ,
2019-01-01 19:00:16 +01:00
accountName : "*" ,
2016-10-11 15:51:46 +02:00
nick : "*" , // * is used until actual nick is given
nickCasefolded : "*" ,
2016-09-19 14:30:29 +02:00
nickMaskString : "*" , // * is used until actual nick is given
2018-11-26 11:23:27 +01:00
history : history . NewHistoryBuffer ( config . History . ClientLength ) ,
2012-12-09 21:51:50 +01:00
}
2019-04-12 06:08:46 +02:00
session := & Session {
client : client ,
socket : socket ,
capVersion : caps . Cap301 ,
capState : caps . NoneState ,
}
session . SetMaxlenRest ( )
client . sessions = [ ] * Session { session }
2016-09-07 13:32:58 +02:00
2019-02-26 03:50:43 +01:00
if conn . IsTLS {
client . SetMode ( modes . TLS , true )
2016-09-07 13:32:58 +02:00
// error is not useful to us here anyways so we can ignore it
2019-04-12 06:08:46 +02:00
client . certfp , _ = socket . CertFP ( )
2016-06-28 17:09:07 +02:00
}
2019-02-26 03:50:43 +01:00
if conn . IsTor {
client . SetMode ( modes . TLS , true )
client . realIP = utils . IPv4LoopbackAddress
client . rawHostname = config . Server . TorListeners . Vhost
} else {
remoteAddr := conn . Conn . RemoteAddr ( )
client . realIP = utils . AddrToIP ( remoteAddr )
// Set the hostname for this client
// (may be overridden by a later PROXY command from stunnel)
client . rawHostname = utils . LookupHostname ( client . realIP . String ( ) )
if config . Server . CheckIdent && ! utils . AddrIsUnix ( remoteAddr ) {
client . doIdentLookup ( conn . Conn )
2016-06-30 11:28:34 +02:00
}
2019-02-26 03:50:43 +01:00
}
2019-04-12 06:08:46 +02:00
client . run ( session )
2019-02-26 03:50:43 +01:00
}
func ( client * Client ) doIdentLookup ( conn net . Conn ) {
_ , serverPortString , err := net . SplitHostPort ( conn . LocalAddr ( ) . String ( ) )
if err != nil {
client . server . logger . Error ( "internal" , "bad server address" , err . Error ( ) )
return
}
serverPort , _ := strconv . Atoi ( serverPortString )
clientHost , clientPortString , err := net . SplitHostPort ( conn . RemoteAddr ( ) . String ( ) )
if err != nil {
client . server . logger . Error ( "internal" , "bad client address" , err . Error ( ) )
return
}
clientPort , _ := strconv . Atoi ( clientPortString )
2016-06-30 11:28:34 +02:00
2019-02-26 03:50:43 +01:00
client . Notice ( client . t ( "*** Looking up your username" ) )
resp , err := ident . Query ( clientHost , serverPort , clientPort , IdentTimeoutSeconds )
if err == nil {
err := client . SetNames ( resp . Identifier , "" , true )
2016-06-30 11:28:34 +02:00
if err == nil {
2019-02-26 03:50:43 +01:00
client . Notice ( client . t ( "*** Found your username" ) )
// we don't need to updateNickMask here since nickMask is not used for anything yet
2016-06-30 11:28:34 +02:00
} else {
2019-02-26 03:50:43 +01:00
client . Notice ( client . t ( "*** Got a malformed username, ignoring" ) )
2016-06-30 11:28:34 +02:00
}
2019-02-26 03:50:43 +01:00
} else {
client . Notice ( client . t ( "*** Could not find your username" ) )
2016-06-30 11:28:34 +02:00
}
2012-04-07 20:44:59 +02:00
}
2019-02-05 06:19:03 +01:00
func ( client * Client ) isAuthorized ( config * Config ) bool {
saslSent := client . account != ""
2019-02-26 03:50:43 +01:00
// PASS requirement
2019-02-26 04:56:08 +01:00
if ( config . Server . passwordBytes != nil ) && ! client . sentPassCommand && ! ( config . Accounts . SkipServerPassword && saslSent ) {
2019-02-05 06:19:03 +01:00
return false
}
2019-02-26 03:50:43 +01:00
// Tor connections may be required to authenticate with SASL
2019-02-26 04:56:08 +01:00
if client . isTor && config . Server . TorListeners . RequireSasl && ! saslSent {
2019-02-26 03:50:43 +01:00
return false
}
// finally, enforce require-sasl
return ! config . Accounts . RequireSasl . Enabled || saslSent || utils . IPInNets ( client . IP ( ) , config . Accounts . RequireSasl . exemptedNets )
2019-02-05 06:19:03 +01:00
}
2019-04-12 06:08:46 +02:00
func ( session * Session ) resetFakelag ( ) {
var flc FakelagConfig = session . client . server . Config ( ) . Fakelag
flc . Enabled = flc . Enabled && ! session . client . HasRoleCapabs ( "nofakelag" )
session . fakelag . Initialize ( flc )
2018-03-22 16:04:21 +01:00
}
2017-05-24 08:58:36 +02:00
// IP returns the IP address of this client.
func ( client * Client ) IP ( ) net . IP {
2019-02-05 06:19:03 +01:00
client . stateMutex . RLock ( )
defer client . stateMutex . RUnlock ( )
2018-02-01 21:53:49 +01:00
if client . proxiedIP != nil {
return client . proxiedIP
2017-09-11 07:04:08 +02:00
}
2019-02-05 06:19:03 +01:00
return client . realIP
2017-05-24 08:58:36 +02:00
}
2017-06-22 21:15:10 +02:00
// IPString returns the IP address of this client as a string.
func ( client * Client ) IPString ( ) string {
ip := client . IP ( ) . String ( )
if 0 < len ( ip ) && ip [ 0 ] == ':' {
ip = "0" + ip
}
return ip
}
2014-02-24 07:21:39 +01:00
//
// command goroutine
//
2019-04-12 06:08:46 +02:00
func ( client * Client ) run ( session * Session ) {
2014-04-15 17:49:52 +02:00
2017-10-24 00:38:32 +02:00
defer func ( ) {
2017-10-26 11:15:55 +02:00
if r := recover ( ) ; r != nil {
client . server . logger . Error ( "internal" ,
fmt . Sprintf ( "Client caused panic: %v\n%s" , r , debug . Stack ( ) ) )
if client . server . RecoverFromErrors ( ) {
client . server . logger . Error ( "internal" , "Disconnecting client and attempting to recover" )
} else {
panic ( r )
2017-10-26 10:19:01 +02:00
}
2017-10-24 00:38:32 +02:00
}
// ensure client connection gets closed
2019-04-12 06:08:46 +02:00
client . destroy ( false , session )
2017-10-24 00:38:32 +02:00
} ( )
2019-04-12 06:08:46 +02:00
session . idletimer . Initialize ( session )
session . resetFakelag ( )
2017-10-15 18:24:28 +02:00
2019-04-12 06:08:46 +02:00
isReattach := client . Registered ( )
// don't reset the nick timer during a reattach
if ! isReattach {
client . nickTimer . Initialize ( client )
}
2018-03-22 16:04:21 +01:00
2018-09-03 06:19:10 +02:00
firstLine := true
2016-06-17 14:17:42 +02:00
for {
2019-04-12 06:08:46 +02:00
maxlenRest := session . MaxlenRest ( )
2017-10-23 01:50:16 +02:00
2019-04-12 06:08:46 +02:00
line , err := session . socket . Read ( )
2016-06-17 14:17:42 +02:00
if err != nil {
2018-03-18 02:32:12 +01:00
quitMessage := "connection closed"
if err == errReadQ {
quitMessage = "readQ exceeded"
}
2019-04-12 06:08:46 +02:00
client . Quit ( quitMessage , session )
2016-06-17 14:17:42 +02:00
break
}
2018-10-28 20:44:13 +01:00
if client . server . logger . IsLoggingRawIO ( ) {
client . server . logger . Debug ( "userinput" , client . nick , "<- " , line )
}
2018-09-03 06:19:10 +02:00
// special-cased handling of PROXY protocol, see `handleProxyCommand` for details:
2019-04-12 06:08:46 +02:00
if ! isReattach && firstLine {
2018-09-03 06:19:10 +02:00
firstLine = false
if strings . HasPrefix ( line , "PROXY" ) {
2019-04-12 06:08:46 +02:00
err = handleProxyCommand ( client . server , client , session , line )
2018-09-03 06:19:10 +02:00
if err != nil {
break
} else {
continue
}
}
}
2017-03-06 13:11:10 +01:00
2019-04-12 06:08:46 +02:00
msg , err := ircmsg . ParseLineStrict ( line , true , maxlenRest )
2017-01-18 22:56:33 +01:00
if err == ircmsg . ErrorLineIsEmpty {
continue
2019-03-07 08:31:46 +01:00
} else if err == ircmsg . ErrorLineTooLong {
2019-03-19 08:35:49 +01:00
client . Send ( nil , client . server . name , ERR_INPUTTOOLONG , client . Nick ( ) , client . t ( "Input line too long" ) )
2019-03-07 08:31:46 +01:00
continue
2017-01-18 22:56:33 +01:00
} else if err != nil {
2019-04-12 06:08:46 +02:00
client . Quit ( client . t ( "Received malformed line" ) , session )
2016-06-17 14:17:42 +02:00
break
2014-02-24 07:21:39 +01:00
}
2014-04-15 17:49:52 +02:00
2016-06-19 02:01:30 +02:00
cmd , exists := Commands [ msg . Command ]
if ! exists {
2016-11-04 12:38:47 +01:00
if len ( msg . Command ) > 0 {
2019-03-19 08:35:49 +01:00
client . Send ( nil , client . server . name , ERR_UNKNOWNCOMMAND , client . Nick ( ) , msg . Command , client . t ( "Unknown command" ) )
2016-11-04 12:38:47 +01:00
} else {
2019-03-19 08:35:49 +01:00
client . Send ( nil , client . server . name , ERR_UNKNOWNCOMMAND , client . Nick ( ) , "lastcmd" , client . t ( "No command given" ) )
2016-11-04 12:38:47 +01:00
}
2016-06-20 14:53:45 +02:00
continue
2016-06-19 02:01:30 +02:00
}
2019-04-12 06:08:46 +02:00
isExiting := cmd . Run ( client . server , client , session , msg )
if isExiting {
break
} else if session . client != client {
// bouncer reattach
session . playReattachMessages ( )
go session . client . run ( session )
2016-06-17 14:17:42 +02:00
break
}
2014-02-24 07:21:39 +01:00
}
2014-04-15 17:49:52 +02:00
}
2019-04-12 06:08:46 +02:00
func ( session * Session ) playReattachMessages ( ) {
for _ , channel := range session . client . Channels ( ) {
channel . playJoinForSession ( session )
}
}
2016-06-17 14:17:42 +02:00
//
2017-05-09 12:37:48 +02:00
// idle, quit, timers and timeouts
2016-06-17 14:17:42 +02:00
//
2014-04-15 17:49:52 +02:00
2017-05-09 12:37:48 +02:00
// Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
2014-02-18 22:25:21 +01:00
func ( client * Client ) Active ( ) {
2017-12-03 02:05:06 +01:00
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
2014-02-14 03:39:33 +01:00
client . atime = time . Now ( )
2014-02-18 22:25:21 +01:00
}
2014-02-14 03:39:33 +01:00
2017-10-15 18:24:28 +02:00
// Ping sends the client a PING message.
2019-04-12 06:08:46 +02:00
func ( session * Session ) Ping ( ) {
session . Send ( nil , "" , "PING" , session . client . Nick ( ) )
2017-05-09 12:37:48 +02:00
}
2019-02-10 02:01:47 +01:00
// tryResume tries to resume if the client asked us to.
func ( client * Client ) tryResume ( ) ( success bool ) {
2018-01-21 02:23:33 +01:00
server := client . server
2018-11-26 11:23:27 +01:00
config := server . Config ( )
2018-01-21 02:23:33 +01:00
2019-02-10 02:01:47 +01:00
defer func ( ) {
if ! success {
client . resumeDetails = nil
}
} ( )
2018-01-21 02:23:33 +01:00
timestamp := client . resumeDetails . Timestamp
var timestampString string
2018-11-26 11:23:27 +01:00
if ! timestamp . IsZero ( ) {
timestampString = timestamp . UTC ( ) . Format ( IRCv3TimestampFormat )
2018-01-21 02:23:33 +01:00
}
2019-02-12 06:27:57 +01:00
oldClient := server . resumeManager . VerifyToken ( client . resumeDetails . PresentedToken )
2018-11-26 11:23:27 +01:00
if oldClient == nil {
2019-02-12 06:27:57 +01:00
client . Send ( nil , server . name , "RESUME" , "ERR" , client . t ( "Cannot resume connection, token is not valid" ) )
2018-01-21 03:23:47 +01:00
return
}
2018-11-26 11:23:27 +01:00
oldNick := oldClient . Nick ( )
oldNickmask := oldClient . NickMaskString ( )
2018-01-21 03:23:47 +01:00
2018-11-26 11:23:27 +01:00
resumeAllowed := config . Server . AllowPlaintextResume || ( oldClient . HasMode ( modes . TLS ) && client . HasMode ( modes . TLS ) )
if ! resumeAllowed {
2019-02-12 06:27:57 +01:00
client . Send ( nil , server . name , "RESUME" , "ERR" , client . t ( "Cannot resume connection, old and new clients must have TLS" ) )
2018-01-21 02:23:33 +01:00
return
}
2019-02-26 22:39:10 +01:00
if oldClient . isTor != client . isTor {
client . Send ( nil , server . name , "RESUME" , "ERR" , client . t ( "Cannot resume connection from Tor to non-Tor or vice versa" ) )
return
}
2019-04-12 06:08:46 +02:00
if 1 < len ( oldClient . Sessions ( ) ) {
client . Send ( nil , server . name , "RESUME" , "ERR" , client . t ( "Cannot resume a client with multiple attached sessions" ) )
return
}
2018-11-26 11:23:27 +01:00
err := server . clients . Resume ( client , oldClient )
if err != nil {
client . Send ( nil , server . name , "RESUME" , "ERR" , client . t ( "Cannot resume connection" ) )
2018-01-21 02:23:33 +01:00
return
}
2019-02-10 02:01:47 +01:00
success = true
2018-11-26 11:23:27 +01:00
// this is a bit racey
client . resumeDetails . ResumedAt = time . Now ( )
2018-01-21 03:39:09 +01:00
2019-04-15 00:13:01 +02:00
client . nickTimer . Touch ( nil )
2018-11-26 11:23:27 +01:00
// resume successful, proceed to copy client state (nickname, flags, etc.)
// after this, the server thinks that `newClient` owns the nickname
client . resumeDetails . OldClient = oldClient
// transfer monitor stuff
server . monitorManager . Resume ( client , oldClient )
// record the names, not the pointers, of the channels,
// to avoid dumb annoying race conditions
channels := oldClient . Channels ( )
client . resumeDetails . Channels = make ( [ ] string , len ( channels ) )
for i , channel := range channels {
client . resumeDetails . Channels [ i ] = channel . Name ( )
2018-01-21 02:23:33 +01:00
}
2018-11-26 11:23:27 +01:00
username := client . Username ( )
hostname := client . Hostname ( )
friends := make ( ClientSet )
oldestLostMessage := time . Now ( )
// work out how much time, if any, is not covered by history buffers
for _ , channel := range channels {
for _ , member := range channel . Members ( ) {
friends . Add ( member )
lastDiscarded := channel . history . LastDiscarded ( )
if lastDiscarded . Before ( oldestLostMessage ) {
oldestLostMessage = lastDiscarded
}
}
}
2018-12-28 19:45:55 +01:00
privmsgMatcher := func ( item history . Item ) bool {
return item . Type == history . Privmsg || item . Type == history . Notice
}
2019-02-04 11:18:17 +01:00
privmsgHistory := oldClient . history . Match ( privmsgMatcher , false , 0 )
2018-11-26 11:23:27 +01:00
lastDiscarded := oldClient . history . LastDiscarded ( )
if lastDiscarded . Before ( oldestLostMessage ) {
oldestLostMessage = lastDiscarded
}
2018-12-28 19:45:55 +01:00
for _ , item := range privmsgHistory {
// TODO this is the nickmask, fix that
sender := server . clients . Get ( item . Nick )
if sender != nil {
friends . Add ( sender )
2018-11-26 11:23:27 +01:00
}
}
gap := lastDiscarded . Sub ( timestamp )
client . resumeDetails . HistoryIncomplete = gap > 0
gapSeconds := int ( gap . Seconds ( ) ) + 1 // round up to avoid confusion
// send quit/resume messages to friends
for friend := range friends {
2019-04-12 06:08:46 +02:00
for _ , session := range friend . Sessions ( ) {
if session . capabilities . Has ( caps . Resume ) {
if timestamp . IsZero ( ) {
session . Send ( nil , oldNickmask , "RESUMED" , username , hostname )
} else {
session . Send ( nil , oldNickmask , "RESUMED" , username , hostname , timestampString )
}
2018-11-26 11:23:27 +01:00
} else {
2019-04-12 06:08:46 +02:00
if client . resumeDetails . HistoryIncomplete {
session . Send ( nil , oldNickmask , "QUIT" , fmt . Sprintf ( friend . t ( "Client reconnected (up to %d seconds of history lost)" ) , gapSeconds ) )
} else {
session . Send ( nil , oldNickmask , "QUIT" , fmt . Sprintf ( friend . t ( "Client reconnected" ) ) )
}
2018-11-26 11:23:27 +01:00
}
2018-01-21 02:23:33 +01:00
}
}
2018-11-26 11:23:27 +01:00
if client . resumeDetails . HistoryIncomplete {
2018-12-28 19:45:55 +01:00
client . Send ( nil , client . server . name , "RESUME" , "WARN" , fmt . Sprintf ( client . t ( "Resume may have lost up to %d seconds of history" ) , gapSeconds ) )
2018-11-26 11:23:27 +01:00
}
2018-01-21 02:23:33 +01:00
2018-12-28 19:45:55 +01:00
client . Send ( nil , client . server . name , "RESUME" , "SUCCESS" , oldNick )
2018-01-21 04:13:20 +01:00
2018-11-26 11:23:27 +01:00
// after we send the rest of the registration burst, we'll try rejoining channels
2019-02-10 02:01:47 +01:00
return
2018-11-26 11:23:27 +01:00
}
2018-04-24 09:11:11 +02:00
2018-11-26 11:23:27 +01:00
func ( client * Client ) tryResumeChannels ( ) {
details := client . resumeDetails
2018-01-21 02:23:33 +01:00
2018-11-26 11:23:27 +01:00
channels := make ( [ ] * Channel , len ( details . Channels ) )
for _ , name := range details . Channels {
channel := client . server . channels . Get ( name )
if channel == nil {
continue
2018-01-22 11:55:20 +01:00
}
2018-11-26 11:23:27 +01:00
channel . Resume ( client , details . OldClient , details . Timestamp )
channels = append ( channels , channel )
}
2018-01-22 11:55:20 +01:00
2018-11-26 11:23:27 +01:00
// replay direct PRIVSMG history
if ! details . Timestamp . IsZero ( ) {
now := time . Now ( )
2019-02-04 11:18:17 +01:00
items , complete := client . history . Between ( details . Timestamp , now , false , 0 )
2019-04-12 06:08:46 +02:00
rb := NewResponseBuffer ( client . Sessions ( ) [ 0 ] )
2019-02-04 11:18:17 +01:00
client . replayPrivmsgHistory ( rb , items , complete )
rb . Send ( true )
2018-04-24 09:11:11 +02:00
}
2018-01-21 02:23:33 +01:00
2019-04-12 06:08:46 +02:00
details . OldClient . destroy ( true , nil )
2018-11-26 11:23:27 +01:00
}
2019-02-04 11:18:17 +01:00
func ( client * Client ) replayPrivmsgHistory ( rb * ResponseBuffer , items [ ] history . Item , complete bool ) {
nick := client . Nick ( )
2019-04-12 06:08:46 +02:00
serverTime := rb . session . capabilities . Has ( caps . ServerTime )
2019-02-04 11:18:17 +01:00
for _ , item := range items {
var command string
switch item . Type {
case history . Privmsg :
command = "PRIVMSG"
case history . Notice :
command = "NOTICE"
default :
continue
}
2019-03-07 08:31:46 +01:00
var tags map [ string ] string
2019-02-04 11:18:17 +01:00
if serverTime {
2019-03-07 08:31:46 +01:00
tags = map [ string ] string { "time" : item . Time . Format ( IRCv3TimestampFormat ) }
2019-02-04 11:18:17 +01:00
}
2019-03-07 08:31:46 +01:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , tags , command , nick , item . Message )
2019-02-04 11:18:17 +01:00
}
if ! complete {
rb . Add ( nil , "HistServ" , "NOTICE" , nick , client . t ( "Some additional message history may have been lost" ) )
}
}
2018-11-26 11:23:27 +01:00
// copy applicable state from oldClient to client as part of a resume
func ( client * Client ) copyResumeData ( oldClient * Client ) {
oldClient . stateMutex . RLock ( )
history := oldClient . history
nick := oldClient . nick
nickCasefolded := oldClient . nickCasefolded
vhost := oldClient . vhost
account := oldClient . account
accountName := oldClient . accountName
2019-01-31 00:59:49 +01:00
skeleton := oldClient . skeleton
2018-11-26 11:23:27 +01:00
oldClient . stateMutex . RUnlock ( )
// copy all flags, *except* TLS (in the case that the admins enabled
// resume over plaintext)
hasTLS := client . flags . HasMode ( modes . TLS )
temp := modes . NewModeSet ( )
2019-03-12 00:24:45 +01:00
temp . Copy ( & oldClient . flags )
2018-11-26 11:23:27 +01:00
temp . SetMode ( modes . TLS , hasTLS )
client . flags . Copy ( temp )
2018-01-21 02:23:33 +01:00
2018-11-26 11:23:27 +01:00
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
2018-01-21 02:23:33 +01:00
2018-11-26 11:23:27 +01:00
// reuse the old client's history buffer
client . history = history
// copy other data
client . nick = nick
client . nickCasefolded = nickCasefolded
client . vhost = vhost
client . account = account
client . accountName = accountName
2019-01-31 00:59:49 +01:00
client . skeleton = skeleton
2018-11-26 11:23:27 +01:00
client . updateNickMaskNoMutex ( )
2018-01-21 02:23:33 +01:00
}
2016-10-23 03:48:57 +02:00
// IdleTime returns how long this client's been idle.
2014-02-18 00:25:32 +01:00
func ( client * Client ) IdleTime ( ) time . Duration {
2017-12-03 02:05:06 +01:00
client . stateMutex . RLock ( )
defer client . stateMutex . RUnlock ( )
2014-02-18 00:25:32 +01:00
return time . Since ( client . atime )
}
2016-10-23 03:48:57 +02:00
// SignonTime returns this client's signon time as a unix timestamp.
2014-02-18 04:56:06 +01:00
func ( client * Client ) SignonTime ( ) int64 {
return client . ctime . Unix ( )
}
2016-10-23 03:48:57 +02:00
// IdleSeconds returns the number of seconds this client's been idle.
2014-02-18 04:08:57 +01:00
func ( client * Client ) IdleSeconds ( ) uint64 {
return uint64 ( client . IdleTime ( ) . Seconds ( ) )
}
2016-10-23 03:48:57 +02:00
// HasNick returns true if the client's nickname is set (used in registration).
2014-02-09 02:10:04 +01:00
func ( client * Client ) HasNick ( ) bool {
2017-11-22 10:41:11 +01:00
client . stateMutex . RLock ( )
defer client . stateMutex . RUnlock ( )
2016-10-11 15:51:46 +02:00
return client . nick != "" && client . nick != "*"
2012-12-17 04:13:53 +01:00
}
2017-04-16 03:31:33 +02:00
// HasUsername returns true if the client's username is set (used in registration).
2014-02-09 02:10:04 +01:00
func ( client * Client ) HasUsername ( ) bool {
2017-11-22 10:41:11 +01:00
client . stateMutex . RLock ( )
defer client . stateMutex . RUnlock ( )
2016-10-11 15:51:46 +02:00
return client . username != "" && client . username != "*"
2014-02-09 02:10:04 +01:00
}
2019-02-03 09:49:42 +01:00
// SetNames sets the client's ident and realname.
2019-02-05 08:40:49 +01:00
func ( client * Client ) SetNames ( username , realname string , fromIdent bool ) error {
limit := client . server . Config ( ) . Limits . IdentLen
if ! fromIdent {
limit -= 1 // leave room for the prepended ~
}
2019-02-05 09:04:52 +01:00
if limit < len ( username ) {
2019-02-05 08:40:49 +01:00
username = username [ : limit ]
}
2019-02-03 09:49:42 +01:00
if ! isIdent ( username ) {
2018-11-26 11:23:27 +01:00
return errInvalidUsername
}
2019-02-05 08:40:49 +01:00
if ! fromIdent {
username = "~" + username
}
2019-02-03 09:49:42 +01:00
2018-11-26 11:23:27 +01:00
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
if client . username == "" {
2019-02-05 08:40:49 +01:00
client . username = username
2018-11-26 11:23:27 +01:00
}
if client . realname == "" {
client . realname = realname
}
return nil
}
2017-09-29 04:11:06 +02:00
// HasRoleCapabs returns true if client has the given (role) capabilities.
func ( client * Client ) HasRoleCapabs ( capabs ... string ) bool {
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil {
2016-10-23 03:13:08 +02:00
return false
}
for _ , capab := range capabs {
2018-04-19 08:48:19 +02:00
if ! oper . Class . Capabilities [ capab ] {
2016-10-23 03:13:08 +02:00
return false
}
}
return true
}
2017-04-16 03:31:33 +02:00
// ModeString returns the mode string for this client.
func ( client * Client ) ModeString ( ) ( str string ) {
2018-04-23 00:47:10 +02:00
return "+" + client . flags . String ( )
2012-04-18 05:24:26 +02:00
}
2012-04-18 06:13:12 +02:00
2016-06-17 14:17:42 +02:00
// Friends refers to clients that share a channel with this client.
2019-04-12 06:08:46 +02:00
func ( client * Client ) Friends ( capabs ... caps . Capability ) ( result map [ * Session ] bool ) {
result = make ( map [ * Session ] bool )
2016-10-26 16:44:36 +02:00
2019-04-12 06:08:46 +02:00
// look at the client's own sessions
for _ , session := range client . Sessions ( ) {
if session . capabilities . HasAll ( capabs ... ) {
result [ session ] = true
2016-10-26 16:44:36 +02:00
}
}
2017-10-23 01:50:16 +02:00
for _ , channel := range client . Channels ( ) {
for _ , member := range channel . Members ( ) {
2019-04-12 06:08:46 +02:00
for _ , session := range member . Sessions ( ) {
if session . capabilities . HasAll ( capabs ... ) {
result [ session ] = true
2016-10-13 10:08:08 +02:00
}
}
2014-02-19 00:28:20 +01:00
}
2014-02-17 02:23:47 +01:00
}
2019-04-12 06:08:46 +02:00
return
2014-02-17 02:23:47 +01:00
}
2019-01-31 00:59:49 +01:00
func ( client * Client ) SetOper ( oper * Oper ) {
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
client . oper = oper
// operators typically get a vhost, update the nickmask
client . updateNickMaskNoMutex ( )
}
2018-04-19 08:48:19 +02:00
// XXX: CHGHOST requires prefix nickmask to have original hostname,
// this is annoying to do correctly
func ( client * Client ) sendChghost ( oldNickMask string , vhost string ) {
username := client . Username ( )
for fClient := range client . Friends ( caps . ChgHost ) {
2018-11-26 11:23:27 +01:00
fClient . sendFromClientInternal ( false , time . Time { } , "" , oldNickMask , client . AccountName ( ) , nil , "CHGHOST" , username , vhost )
2018-04-19 08:48:19 +02:00
}
}
// choose the correct vhost to display
func ( client * Client ) getVHostNoMutex ( ) string {
// hostserv vhost OR operclass vhost OR nothing (i.e., normal rdns hostmask)
if client . vhost != "" {
return client . vhost
} else if client . oper != nil {
return client . oper . Vhost
} else {
return ""
}
}
// SetVHost updates the client's hostserv-based vhost
func ( client * Client ) SetVHost ( vhost string ) ( updated bool ) {
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
updated = ( client . vhost != vhost )
client . vhost = vhost
if updated {
client . updateNickMaskNoMutex ( )
}
return
}
2017-10-04 06:57:03 +02:00
// updateNick updates `nick` and `nickCasefolded`.
2019-01-31 00:59:49 +01:00
func ( client * Client ) updateNick ( nick , nickCasefolded , skeleton string ) {
2017-10-04 06:57:03 +02:00
client . stateMutex . Lock ( )
2019-01-31 00:59:49 +01:00
defer client . stateMutex . Unlock ( )
2017-10-04 06:57:03 +02:00
client . nick = nick
2019-01-31 00:59:49 +01:00
client . nickCasefolded = nickCasefolded
client . skeleton = skeleton
client . updateNickMaskNoMutex ( )
2016-10-16 12:35:50 +02:00
}
2019-01-31 00:59:49 +01:00
// updateNickMaskNoMutex updates the casefolded nickname and nickmask, not acquiring any mutexes.
2018-01-22 11:55:20 +01:00
func ( client * Client ) updateNickMaskNoMutex ( ) {
2018-04-19 08:48:19 +02:00
client . hostname = client . getVHostNoMutex ( )
if client . hostname == "" {
2016-10-23 03:28:31 +02:00
client . hostname = client . rawHostname
}
2019-01-28 19:36:15 +01:00
cfhostname , err := Casefold ( client . hostname )
2016-10-11 15:51:46 +02:00
if err != nil {
2019-01-28 19:36:15 +01:00
client . server . logger . Error ( "internal" , "hostname couldn't be casefolded" , client . hostname , err . Error ( ) )
cfhostname = client . hostname // YOLO
2016-10-11 15:51:46 +02:00
}
2017-10-04 06:57:03 +02:00
2019-01-28 19:36:15 +01:00
client . nickMaskString = fmt . Sprintf ( "%s!%s@%s" , client . nick , client . username , client . hostname )
2019-02-05 08:40:49 +01:00
client . nickMaskCasefolded = fmt . Sprintf ( "%s!%s@%s" , client . nickCasefolded , strings . ToLower ( client . username ) , cfhostname )
2016-06-19 07:37:29 +02:00
}
2017-01-11 13:38:16 +01:00
// AllNickmasks returns all the possible nickmasks for the client.
2019-01-29 05:03:30 +01:00
func ( client * Client ) AllNickmasks ( ) ( masks [ ] string ) {
2018-04-19 08:48:19 +02:00
client . stateMutex . RLock ( )
2019-01-29 05:03:30 +01:00
nick := client . nickCasefolded
2019-02-05 08:40:49 +01:00
username := client . username
2018-04-19 08:48:19 +02:00
rawHostname := client . rawHostname
vhost := client . getVHostNoMutex ( )
client . stateMutex . RUnlock ( )
2019-02-05 09:04:52 +01:00
username = strings . ToLower ( username )
2018-04-19 08:48:19 +02:00
if len ( vhost ) > 0 {
2019-01-29 05:03:30 +01:00
cfvhost , err := Casefold ( vhost )
2017-01-11 13:38:16 +01:00
if err == nil {
2019-01-29 05:03:30 +01:00
masks = append ( masks , fmt . Sprintf ( "%s!%s@%s" , nick , username , cfvhost ) )
2017-01-11 13:38:16 +01:00
}
}
2019-01-29 05:03:30 +01:00
var rawhostmask string
cfrawhost , err := Casefold ( rawHostname )
2017-01-11 13:38:16 +01:00
if err == nil {
2019-01-29 05:03:30 +01:00
rawhostmask = fmt . Sprintf ( "%s!%s@%s" , nick , username , cfrawhost )
masks = append ( masks , rawhostmask )
2017-01-11 13:38:16 +01:00
}
2019-01-29 05:03:30 +01:00
ipmask := fmt . Sprintf ( "%s!%s@%s" , nick , username , client . IPString ( ) )
if ipmask != rawhostmask {
masks = append ( masks , ipmask )
2017-01-11 13:38:16 +01:00
}
2019-01-29 05:03:30 +01:00
return
2017-01-11 13:38:16 +01:00
}
2017-09-28 07:49:01 +02:00
// LoggedIntoAccount returns true if this client is logged into an account.
func ( client * Client ) LoggedIntoAccount ( ) bool {
2018-02-11 11:30:40 +01:00
return client . Account ( ) != ""
2017-09-28 07:49:01 +02:00
}
2017-10-05 15:39:57 +02:00
// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
2018-02-05 15:21:08 +01:00
func ( client * Client ) RplISupport ( rb * ResponseBuffer ) {
2018-01-23 14:31:29 +01:00
translatedISupport := client . t ( "are supported by this server" )
2018-04-20 20:13:25 +02:00
nick := client . Nick ( )
for _ , cachedTokenLine := range client . server . ISupport ( ) . CachedReply {
length := len ( cachedTokenLine ) + 2
tokenline := make ( [ ] string , length )
tokenline [ 0 ] = nick
copy ( tokenline [ 1 : ] , cachedTokenLine )
tokenline [ length - 1 ] = translatedISupport
rb . Add ( nil , client . server . name , RPL_ISUPPORT , tokenline ... )
2017-10-05 15:39:57 +02:00
}
}
2019-02-10 19:57:32 +01:00
// Quit sets the given quit message for the client.
// (You must ensure separately that destroy() is called, e.g., by returning `true` from
// the command handler or calling it yourself.)
2019-04-12 06:08:46 +02:00
func ( client * Client ) Quit ( message string , session * Session ) {
setFinalData := func ( sess * Session ) {
message := sess . quitMessage
var finalData [ ] byte
// #364: don't send QUIT lines to unregistered clients
if client . registered {
quitMsg := ircmsg . MakeMessage ( nil , client . nickMaskString , "QUIT" , message )
finalData , _ = quitMsg . LineBytesStrict ( false , 512 )
}
2017-10-11 02:49:29 +02:00
2019-04-12 06:08:46 +02:00
errorMsg := ircmsg . MakeMessage ( nil , "" , "ERROR" , message )
errorMsgBytes , _ := errorMsg . LineBytesStrict ( false , 512 )
finalData = append ( finalData , errorMsgBytes ... )
2017-10-11 02:49:29 +02:00
2019-04-12 06:08:46 +02:00
sess . socket . SetFinalData ( finalData )
2019-02-10 19:57:32 +01:00
}
2017-10-11 02:49:29 +02:00
2019-04-12 06:08:46 +02:00
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
var sessions [ ] * Session
if session != nil {
sessions = [ ] * Session { session }
} else {
sessions = client . sessions
}
2017-10-11 02:49:29 +02:00
2019-04-12 06:08:46 +02:00
for _ , session := range sessions {
if session . SetQuitMessage ( message ) {
setFinalData ( session )
}
}
2016-06-17 14:17:42 +02:00
}
2016-10-23 03:48:57 +02:00
// destroy gets rid of a client, removes them from server lists etc.
2019-04-12 06:08:46 +02:00
// if `session` is nil, destroys the client unconditionally, removing all sessions;
// otherwise, destroys one specific session, only destroying the client if it
// has no more sessions.
func ( client * Client ) destroy ( beingResumed bool , session * Session ) {
var sessionsToDestroy [ ] * Session
2017-10-11 18:48:06 +02:00
// allow destroy() to execute at most once
2018-11-26 11:23:27 +01:00
client . stateMutex . Lock ( )
nickMaskString := client . nickMaskString
accountName := client . accountName
2019-04-12 06:08:46 +02:00
alreadyDestroyed := len ( client . sessions ) == 0
sessionRemoved := false
var remainingSessions int
if session == nil {
sessionRemoved = ! alreadyDestroyed
sessionsToDestroy = client . sessions
client . sessions = nil
remainingSessions = 0
} else {
sessionRemoved , remainingSessions = client . removeSession ( session )
if sessionRemoved {
sessionsToDestroy = [ ] * Session { session }
}
}
var quitMessage string
if 0 < len ( sessionsToDestroy ) {
quitMessage = sessionsToDestroy [ 0 ] . quitMessage
}
2018-11-26 11:23:27 +01:00
client . stateMutex . Unlock ( )
2019-04-12 06:08:46 +02:00
if alreadyDestroyed || ! sessionRemoved {
return
}
for _ , session := range sessionsToDestroy {
if session . client != client {
// session has been attached to a new client; do not destroy it
continue
}
session . idletimer . Stop ( )
session . socket . Close ( )
// send quit/error message to client if they haven't been sent already
client . Quit ( "" , session )
}
if remainingSessions != 0 {
2018-01-22 11:55:20 +01:00
return
2014-02-18 22:25:21 +01:00
}
2014-02-20 03:46:46 +01:00
2018-04-25 02:34:28 +02:00
// see #235: deduplicating the list of PART recipients uses (comparatively speaking)
// a lot of RAM, so limit concurrency to avoid thrashing
client . server . semaphores . ClientDestroy . Acquire ( )
defer client . server . semaphores . ClientDestroy . Release ( )
2018-01-21 02:23:33 +01:00
if beingResumed {
client . server . logger . Debug ( "quit" , fmt . Sprintf ( "%s is being resumed" , client . nick ) )
} else {
client . server . logger . Debug ( "quit" , fmt . Sprintf ( "%s is no longer on the server" , client . nick ) )
}
2017-03-06 13:11:10 +01:00
2018-01-21 02:23:33 +01:00
if ! beingResumed {
2018-05-04 06:24:54 +02:00
client . server . whoWas . Append ( client . WhoWas ( ) )
2018-01-21 02:23:33 +01:00
}
2016-06-17 14:17:42 +02:00
2016-10-23 15:05:00 +02:00
// remove from connection limits
2019-02-26 03:50:43 +01:00
if client . isTor {
client . server . torLimiter . RemoveClient ( )
} else {
client . server . connectionLimiter . RemoveClient ( client . IP ( ) )
2016-10-23 15:05:00 +02:00
}
2019-02-12 06:27:57 +01:00
client . server . resumeManager . Delete ( client )
2016-10-16 12:14:56 +02:00
// alert monitors
2017-10-04 08:59:59 +02:00
client . server . monitorManager . AlertAbout ( client , false )
2017-10-04 06:57:03 +02:00
// clean up monitor state
2017-10-04 08:59:59 +02:00
client . server . monitorManager . RemoveAll ( client )
2016-10-16 12:14:56 +02:00
2016-06-17 14:17:42 +02:00
// clean up channels
2018-04-25 02:23:01 +02:00
friends := make ( ClientSet )
2017-10-30 10:21:47 +01:00
for _ , channel := range client . Channels ( ) {
2018-01-21 02:23:33 +01:00
if ! beingResumed {
channel . Quit ( client )
2018-11-26 11:23:27 +01:00
channel . history . Add ( history . Item {
Type : history . Quit ,
Nick : nickMaskString ,
AccountName : accountName ,
Message : utils . MakeSplitMessage ( quitMessage , true ) ,
} )
2018-01-21 02:23:33 +01:00
}
2017-10-23 01:50:16 +02:00
for _ , member := range channel . Members ( ) {
friends . Add ( member )
}
2016-06-17 14:17:42 +02:00
}
2018-04-25 02:23:01 +02:00
friends . Remove ( client )
2016-06-17 14:17:42 +02:00
// clean up server
2018-01-21 02:23:33 +01:00
if ! beingResumed {
client . server . clients . Remove ( client )
}
2016-06-17 14:17:42 +02:00
// clean up self
2018-02-28 06:14:44 +01:00
client . nickTimer . Stop ( )
2016-06-17 14:17:42 +02:00
2018-02-11 11:30:40 +01:00
client . server . accounts . Logout ( client )
2016-11-29 12:06:01 +01:00
// send quit messages to friends
2018-01-21 02:23:33 +01:00
if ! beingResumed {
2018-08-08 23:56:12 +02:00
if client . Registered ( ) {
client . server . stats . ChangeTotal ( - 1 )
}
2018-04-20 22:48:15 +02:00
if client . HasMode ( modes . Invisible ) {
client . server . stats . ChangeInvisible ( - 1 )
}
if client . HasMode ( modes . Operator ) || client . HasMode ( modes . LocalOperator ) {
client . server . stats . ChangeOperators ( - 1 )
}
2018-01-21 02:23:33 +01:00
for friend := range friends {
2018-11-26 11:23:27 +01:00
if quitMessage == "" {
quitMessage = "Exited"
2018-01-21 02:23:33 +01:00
}
2018-11-26 11:23:27 +01:00
friend . Send ( nil , client . nickMaskString , "QUIT" , quitMessage )
2017-09-26 04:47:03 +02:00
}
2016-11-29 12:06:01 +01:00
}
2017-06-11 18:01:39 +02:00
if ! client . exitedSnomaskSent {
2018-01-21 02:23:33 +01:00
if beingResumed {
client . server . snomasks . Send ( sno . LocalQuits , fmt . Sprintf ( ircfmt . Unescape ( "%s$r is resuming their connection, old client has been destroyed" ) , client . nick ) )
} else {
client . server . snomasks . Send ( sno . LocalQuits , fmt . Sprintf ( ircfmt . Unescape ( "%s$r exited the network" ) , client . nick ) )
}
2017-06-11 18:01:39 +02:00
}
2016-06-19 02:01:30 +02:00
}
2014-02-18 22:25:21 +01:00
2017-01-14 06:28:50 +01:00
// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
// Adds account-tag to the line as well.
2019-04-12 06:08:46 +02:00
func ( session * Session ) sendSplitMsgFromClientInternal ( blocking bool , serverTime time . Time , nickmask , accountName string , tags map [ string ] string , command , target string , message utils . SplitMessage ) {
if session . capabilities . Has ( caps . MaxLine ) || message . Wrapped == nil {
session . sendFromClientInternal ( blocking , serverTime , message . Msgid , nickmask , accountName , tags , command , target , message . Message )
2017-01-14 06:28:50 +01:00
} else {
2019-03-07 08:31:46 +01:00
for _ , messagePair := range message . Wrapped {
2019-04-12 06:08:46 +02:00
session . sendFromClientInternal ( blocking , serverTime , messagePair . Msgid , nickmask , accountName , tags , command , target , messagePair . Message )
2017-01-14 06:28:50 +01:00
}
}
}
2016-09-12 03:25:31 +02:00
// SendFromClient sends an IRC line coming from a specific client.
// Adds account-tag to the line as well.
2019-03-07 08:31:46 +01:00
func ( client * Client ) SendFromClient ( msgid string , from * Client , tags map [ string ] string , command string , params ... string ) error {
2018-11-26 11:23:27 +01:00
return client . sendFromClientInternal ( false , time . Time { } , msgid , from . NickMaskString ( ) , from . AccountName ( ) , tags , command , params ... )
}
2019-03-07 08:31:46 +01:00
// this is SendFromClient, but directly exposing nickmask and accountName,
// for things like history replay and CHGHOST where they no longer (necessarily)
// correspond to the current state of a client
2019-04-12 06:08:46 +02:00
func ( client * Client ) sendFromClientInternal ( blocking bool , serverTime time . Time , msgid string , nickmask , accountName string , tags map [ string ] string , command string , params ... string ) ( err error ) {
for _ , session := range client . Sessions ( ) {
err_ := session . sendFromClientInternal ( blocking , serverTime , msgid , nickmask , accountName , tags , command , params ... )
if err_ != nil {
err = err_
}
}
return
}
func ( session * Session ) sendFromClientInternal ( blocking bool , serverTime time . Time , msgid string , nickmask , accountName string , tags map [ string ] string , command string , params ... string ) ( err error ) {
2019-03-07 08:31:46 +01:00
msg := ircmsg . MakeMessage ( tags , nickmask , command , params ... )
2016-09-12 03:25:31 +02:00
// attach account-tag
2019-04-12 06:08:46 +02:00
if session . capabilities . Has ( caps . AccountTag ) && accountName != "*" {
2019-03-07 08:31:46 +01:00
msg . SetTag ( "account" , accountName )
2016-09-12 03:25:31 +02:00
}
2017-01-14 10:52:47 +01:00
// attach message-id
2019-04-12 06:08:46 +02:00
if msgid != "" && session . capabilities . Has ( caps . MessageTags ) {
2019-03-07 08:31:46 +01:00
msg . SetTag ( "draft/msgid" , msgid )
}
// attach server-time
2019-04-12 06:08:46 +02:00
if session . capabilities . Has ( caps . ServerTime ) {
2019-03-07 08:31:46 +01:00
msg . SetTag ( "time" , time . Now ( ) . UTC ( ) . Format ( IRCv3TimestampFormat ) )
2017-01-14 10:52:47 +01:00
}
2016-09-12 03:25:31 +02:00
2019-04-12 06:08:46 +02:00
return session . SendRawMessage ( msg , blocking )
2016-09-12 03:25:31 +02:00
}
2017-01-20 15:07:10 +01:00
var (
2017-01-23 00:03:49 +01:00
// these are all the output commands that MUST have their last param be a trailing.
2017-10-08 03:05:05 +02:00
// this is needed because dumb clients like to treat trailing params separately from the
2017-01-23 00:03:49 +01:00
// other params in messages.
2017-01-20 15:07:10 +01:00
commandsThatMustUseTrailing = map [ string ] bool {
"PRIVMSG" : true ,
"NOTICE" : true ,
2017-01-23 00:03:49 +01:00
RPL_WHOISCHANNELS : true ,
2017-03-06 06:50:23 +01:00
RPL_USERHOST : true ,
2017-01-20 15:07:10 +01:00
}
)
2017-10-08 03:05:05 +02:00
// SendRawMessage sends a raw message to the client.
2019-04-12 06:08:46 +02:00
func ( session * Session ) SendRawMessage ( message ircmsg . IrcMessage , blocking bool ) error {
2017-10-08 03:05:05 +02:00
// use dumb hack to force the last param to be a trailing param if required
2017-05-09 12:37:48 +02:00
var usedTrailingHack bool
2019-03-07 08:31:46 +01:00
if commandsThatMustUseTrailing [ message . Command ] && len ( message . Params ) > 0 {
2017-10-08 03:05:05 +02:00
lastParam := message . Params [ len ( message . Params ) - 1 ]
2017-05-09 12:37:48 +02:00
// to force trailing, we ensure the final param contains a space
2019-03-07 08:31:46 +01:00
if strings . IndexByte ( lastParam , ' ' ) == - 1 {
2017-10-08 03:05:05 +02:00
message . Params [ len ( message . Params ) - 1 ] = lastParam + " "
2017-05-09 12:37:48 +02:00
usedTrailingHack = true
2017-01-20 14:51:36 +01:00
}
}
2017-10-08 03:05:05 +02:00
// assemble message
2019-04-12 06:08:46 +02:00
maxlenRest := session . MaxlenRest ( )
2019-03-07 08:31:46 +01:00
line , err := message . LineBytesStrict ( false , maxlenRest )
2016-06-19 02:01:30 +02:00
if err != nil {
2018-02-11 11:30:40 +01:00
logline := fmt . Sprintf ( "Error assembling message for sending: %v\n%s" , err , debug . Stack ( ) )
2019-04-12 06:08:46 +02:00
session . client . server . logger . Error ( "internal" , logline )
2016-11-04 12:38:47 +01:00
2019-04-12 06:08:46 +02:00
message = ircmsg . MakeMessage ( nil , session . client . server . name , ERR_UNKNOWNERROR , "*" , "Error assembling message for sending" )
2019-03-07 08:31:46 +01:00
line , _ := message . LineBytesStrict ( false , 0 )
2017-10-08 03:05:05 +02:00
2018-11-26 11:23:27 +01:00
if blocking {
2019-04-12 06:08:46 +02:00
session . socket . BlockingWrite ( line )
2018-11-26 11:23:27 +01:00
} else {
2019-04-12 06:08:46 +02:00
session . socket . Write ( line )
2018-11-26 11:23:27 +01:00
}
2016-06-19 02:01:30 +02:00
return err
2014-02-17 02:23:47 +01:00
}
2017-01-20 14:51:36 +01:00
2018-04-15 18:28:25 +02:00
// if we used the trailing hack, we need to strip the final space we appended earlier on
if usedTrailingHack {
2019-03-07 08:31:46 +01:00
copy ( line [ len ( line ) - 3 : ] , "\r\n" )
2018-04-26 21:32:32 +02:00
line = line [ : len ( line ) - 1 ]
2018-04-15 18:28:25 +02:00
}
2019-04-12 06:08:46 +02:00
if session . client . server . logger . IsLoggingRawIO ( ) {
2018-04-26 21:32:32 +02:00
logline := string ( line [ : len ( line ) - 2 ] ) // strip "\r\n"
2019-04-12 06:08:46 +02:00
session . client . server . logger . Debug ( "useroutput" , session . client . Nick ( ) , " ->" , logline )
2018-04-26 21:32:32 +02:00
}
2017-03-06 13:11:10 +01:00
2018-11-26 11:23:27 +01:00
if blocking {
2019-04-12 06:08:46 +02:00
return session . socket . BlockingWrite ( line )
2018-11-26 11:23:27 +01:00
} else {
2019-04-12 06:08:46 +02:00
return session . socket . Write ( line )
2018-11-26 11:23:27 +01:00
}
2017-10-08 03:05:05 +02:00
}
2018-11-26 11:23:27 +01:00
// Send sends an IRC line to the client.
2019-04-12 06:08:46 +02:00
func ( client * Client ) Send ( tags map [ string ] string , prefix string , command string , params ... string ) ( err error ) {
for _ , session := range client . Sessions ( ) {
err_ := session . Send ( tags , prefix , command , params ... )
if err_ != nil {
err = err_
}
}
return
}
func ( session * Session ) Send ( tags map [ string ] string , prefix string , command string , params ... string ) ( err error ) {
2019-03-07 08:31:46 +01:00
msg := ircmsg . MakeMessage ( tags , prefix , command , params ... )
2019-04-12 06:08:46 +02:00
if session . capabilities . Has ( caps . ServerTime ) && ! msg . HasTag ( "time" ) {
2019-03-07 08:31:46 +01:00
msg . SetTag ( "time" , time . Now ( ) . UTC ( ) . Format ( IRCv3TimestampFormat ) )
}
2019-04-12 06:08:46 +02:00
return session . SendRawMessage ( msg , false )
2018-11-26 11:23:27 +01:00
}
2016-06-19 02:01:30 +02:00
// Notice sends the client a notice from the server.
func ( client * Client ) Notice ( text string ) {
2019-04-12 06:08:46 +02:00
client . Send ( nil , client . server . name , "NOTICE" , client . Nick ( ) , text )
2014-02-17 02:23:47 +01:00
}
2017-10-23 01:50:16 +02:00
func ( client * Client ) addChannel ( channel * Channel ) {
client . stateMutex . Lock ( )
client . channels [ channel ] = true
client . stateMutex . Unlock ( )
}
func ( client * Client ) removeChannel ( channel * Channel ) {
client . stateMutex . Lock ( )
delete ( client . channels , channel )
client . stateMutex . Unlock ( )
}
2018-11-26 11:23:27 +01:00
2018-12-23 19:25:02 +01:00
// Records that the client has been invited to join an invite-only channel
func ( client * Client ) Invite ( casefoldedChannel string ) {
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
if client . invitedTo == nil {
client . invitedTo = make ( map [ string ] bool )
}
client . invitedTo [ casefoldedChannel ] = true
}
// Checks that the client was invited to join a given channel
func ( client * Client ) CheckInvited ( casefoldedChannel string ) ( invited bool ) {
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
invited = client . invitedTo [ casefoldedChannel ]
// joining an invited channel "uses up" your invite, so you can't rejoin on kick
delete ( client . invitedTo , casefoldedChannel )
return
}