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 (
2020-09-23 08:23:35 +02:00
"crypto/x509"
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
2021-05-27 03:58:29 +02:00
ident "github.com/ergochat/go-ident"
2021-06-18 08:41:57 +02:00
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/irc-go/ircreader"
2021-07-30 18:20:13 +02:00
"github.com/xdg-go/scram"
2021-02-14 02:58:19 +01:00
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/connection_limits"
"github.com/ergochat/ergo/irc/flatip"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
2012-04-07 20:44:59 +02:00
)
2014-03-13 01:52:25 +01:00
const (
2021-05-24 06:38:47 +02:00
// maximum IRC line length, not including tags
DefaultMaxLineLen = 512
2020-08-03 18:51:04 +02:00
2020-05-12 00:25:25 +02:00
// IdentTimeout is how long before our ident (username) check times out.
IdentTimeout = time . Second + 500 * time . Millisecond
2020-02-19 01:38:42 +01:00
IRCv3TimestampFormat = utils . IRCv3TimestampFormat
2020-06-12 21:51:48 +02:00
// limit the number of device IDs a client can use, as a DoS mitigation
maxDeviceIDsPerClient = 64
// controls how often often we write an autoreplay-missed client's
// deviceid->lastseentime mapping to the database
2020-06-27 00:03:39 +02:00
lastSeenWriteInterval = time . Hour
2016-06-30 07:35:34 +02:00
)
2020-08-07 23:30:42 +02:00
const (
// RegisterTimeout is how long clients have to register before we disconnect them
RegisterTimeout = time . Minute
// DefaultIdleTimeout is how long without traffic before we send the client a PING
DefaultIdleTimeout = time . Minute + 30 * time . Second
// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
// (single-onion circuits will close unless the client sends data once every 60 seconds):
// https://bugs.torproject.org/29665
TorIdleTimeout = time . Second * 30
// This is how long a client gets without sending any message, including the PONG to our
// PING, before we disconnect them:
DefaultTotalTimeout = 2 * time . Minute + 30 * time . Second
2020-08-09 04:39:28 +02:00
// round off the ping interval by this much, see below:
PingCoalesceThreshold = time . Second
2020-08-07 23:30:42 +02:00
)
2021-05-24 06:38:47 +02:00
var (
MaxLineLen = DefaultMaxLineLen
)
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
2020-02-19 01:38:42 +01:00
accountRegDate time . Time
2019-05-19 10:27:44 +02:00
accountSettings AccountSettings
2016-10-11 15:51:46 +02:00
awayMessage string
channels ChannelSet
ctime time . Time
2019-05-27 11:52:40 +02:00
destroyed bool
2020-02-19 01:38:42 +01:00
modes modes . ModeSet
2016-10-11 15:51:46 +02:00
hostname string
2020-10-26 01:40:41 +01:00
invitedTo map [ string ] channelInvite
2019-08-27 06:51:09 +02:00
isSTSOnly bool
2018-01-21 07:49:17 +01:00
languages [ ] string
2020-06-12 21:51:48 +02:00
lastActive time . Time // last time they sent a command that wasn't PONG or similar
lastSeen map [ string ] time . Time // maps device ID (including "") to time of last received command
lastSeenLastWrite time . Time // last time `lastSeen` was written to the datastore
2019-01-01 22:45:37 +01:00
loginThrottle connection_limits . GenericThrottle
2020-09-19 19:19:41 +02:00
nextSessionID int64 // Incremented when a new session is established
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
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
2019-05-12 08:17:57 +02:00
cloakedHostname string
2016-10-11 15:51:46 +02:00
realname string
2019-02-05 06:19:03 +01:00
realIP net . IP
2020-09-14 14:11:56 +02:00
requireSASLMessage string
2020-09-14 10:28:12 +02:00
requireSASL bool
2016-10-11 15:51:46 +02:00
registered bool
2020-10-07 00:04:29 +02:00
registerCmdSent bool // already sent the draft/register command, can't send it again
2020-08-07 23:30:42 +02:00
registrationTimer * time . Timer
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
2020-02-19 01:38:42 +01:00
alwaysOn bool
2016-10-11 15:51:46 +02:00
username string
2017-04-18 14:26:01 +02:00
vhost string
2019-05-22 03:40:25 +02:00
history history . Buffer
2020-02-19 01:38:42 +01:00
dirtyBits uint
writerSemaphore utils . Semaphore // tier 1.5
2012-12-17 04:13:53 +01:00
}
2020-02-19 03:42:27 +01:00
type saslStatus struct {
mechanism string
value string
2021-07-30 18:20:13 +02:00
scramConv * scram . ServerConversation
2020-02-19 03:42:27 +01:00
}
func ( s * saslStatus ) Clear ( ) {
* s = saslStatus { }
}
2020-05-18 00:06:20 +02:00
// what stage the client is at w.r.t. the PASS command:
type serverPassStatus uint
const (
serverPassUnsent serverPassStatus = iota
serverPassSuccessful
serverPassFailed
)
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
2020-06-12 21:51:48 +02:00
deviceID string
2020-02-27 08:13:31 +01:00
ctime time . Time
2020-08-07 23:30:42 +02:00
lastActive time . Time // last non-CTCP PRIVMSG sent; updates publicly visible idle time
lastTouch time . Time // last line sent; updates timer for idle timeouts
idleTimer * time . Timer
pingSent bool // we sent PING to a putatively idle connection and we're waiting for PONG
2019-05-08 10:11:54 +02:00
2020-09-19 19:19:41 +02:00
sessionID int64
2019-05-08 10:11:54 +02:00
socket * Socket
realIP net . IP
proxiedIP net . IP
rawHostname string
2020-02-19 01:38:42 +01:00
isTor bool
2020-12-06 05:06:23 +01:00
hideSTS bool
2019-05-08 10:11:54 +02:00
2020-03-27 15:40:19 +01:00
fakelag Fakelag
deferredFakelagCount int
2019-04-12 06:08:46 +02:00
2020-05-18 00:06:20 +02:00
certfp string
2020-09-23 08:23:35 +02:00
peerCerts [ ] * x509 . Certificate
2020-05-18 00:06:20 +02:00
sasl saslStatus
passStatus serverPassStatus
2020-02-19 03:42:27 +01:00
2019-12-23 21:26:37 +01:00
batchCounter uint32
2019-04-12 06:08:46 +02:00
quitMessage string
2021-03-18 07:53:18 +01:00
awayMessage string
awayAt time . Time
2019-04-12 06:08:46 +02:00
capabilities caps . Set
capState caps . State
capVersion caps . Version
2019-05-22 03:40:25 +02:00
2019-05-22 22:15:59 +02:00
registrationMessages int
2020-02-27 08:13:31 +01:00
zncPlaybackTimes * zncPlaybackTimes
autoreplayMissedSince time . Time
2019-12-23 21:26:37 +01:00
batch MultilineBatch
}
// MultilineBatch tracks the state of a client-to-server multiline batch.
type MultilineBatch struct {
label string // this is the first param to BATCH (the "reference tag")
command string
target string
responseLabel string // this is the value of the labeled-response tag sent with BATCH
message utils . SplitMessage
2020-05-15 04:16:34 +02:00
lenBytes int
2019-12-23 21:26:37 +01:00
tags map [ string ] string
2019-04-12 06:08:46 +02:00
}
2020-03-27 15:40:19 +01:00
// Starts a multiline batch, failing if there's one already open
func ( s * Session ) StartMultilineBatch ( label , target , responseLabel string , tags map [ string ] string ) ( err error ) {
if s . batch . label != "" {
return errInvalidMultilineBatch
}
s . batch . label , s . batch . target , s . batch . responseLabel , s . batch . tags = label , target , responseLabel , tags
s . fakelag . Suspend ( )
return
}
// Closes a multiline batch unconditionally; returns the batch and whether
// it was validly terminated (pass "" as the label if you don't care about the batch)
func ( s * Session ) EndMultilineBatch ( label string ) ( batch MultilineBatch , err error ) {
batch = s . batch
s . batch = MultilineBatch { }
s . fakelag . Unsuspend ( )
// heuristics to estimate how much data they used while fakelag was suspended
2020-08-03 18:51:04 +02:00
fakelagBill := ( batch . lenBytes / MaxLineLen ) + 1
fakelagBillLines := ( batch . message . LenLines ( ) * 60 ) / MaxLineLen
2020-03-27 15:40:19 +01:00
if fakelagBill < fakelagBillLines {
fakelagBill = fakelagBillLines
}
s . deferredFakelagCount = fakelagBill
2020-05-14 18:58:49 +02:00
if batch . label == "" || batch . label != label || ! batch . message . ValidMultiline ( ) {
2020-03-27 15:40:19 +01:00
err = errInvalidMultilineBatch
return
}
batch . message . SetTime ( )
return
}
2019-04-12 06:08:46 +02:00
// sets the session quit message, if there isn't one already
2020-12-21 11:11:50 +01:00
func ( sd * Session ) setQuitMessage ( message string ) ( set bool ) {
2019-04-12 06:08:46 +02:00
if message == "" {
2019-05-23 02:29:19 +02:00
message = "Connection closed"
}
if sd . quitMessage == "" {
2019-04-12 06:08:46 +02:00
sd . quitMessage = message
return true
2019-05-23 02:29:19 +02:00
} else {
return false
2019-04-12 06:08:46 +02:00
}
}
2020-02-19 01:38:42 +01:00
func ( s * Session ) IP ( ) net . IP {
if s . proxiedIP != nil {
return s . proxiedIP
}
return s . realIP
}
2019-05-30 01:23:46 +02:00
// returns whether the client supports a smart history replay cap,
// and therefore autoreplay-on-join and similar should be suppressed
func ( session * Session ) HasHistoryCaps ( ) bool {
2020-02-19 01:38:42 +01:00
return session . capabilities . Has ( caps . Chathistory ) || session . capabilities . Has ( caps . ZNCPlayback )
2019-05-30 01:23:46 +02:00
}
2019-12-23 21:26:37 +01:00
// generates a batch ID. the uniqueness requirements for this are fairly weak:
// any two batch IDs that are active concurrently (either through interleaving
// or nesting) on an individual session connection need to be unique.
// this allows ~4 billion such batches which should be fine.
func ( session * Session ) generateBatchID ( ) string {
id := atomic . AddUint32 ( & session . batchCounter , 1 )
2020-05-03 09:27:13 +02:00
return strconv . FormatInt ( int64 ( id ) , 32 )
2019-12-23 21:26:37 +01:00
}
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
2021-06-20 20:13:18 +02:00
ip net . IP
2021-01-19 14:49:45 +01:00
// technically not required for WHOWAS:
account string
accountName string
2019-01-01 19:00:16 +01:00
}
// ClientDetails is a standard set of details about a client
type ClientDetails struct {
WhoWas
nickMask string
nickMaskCasefolded string
}
2019-05-13 02:57:34 +02:00
// RunClient sets up a new client and runs its goroutine.
2020-05-05 04:29:10 +02:00
func ( server * Server ) RunClient ( conn IRCConn ) {
2020-09-14 10:28:12 +02:00
config := server . Config ( )
2020-05-19 15:37:06 +02:00
wConn := conn . UnderlyingConn ( )
2020-09-14 10:28:12 +02:00
var isBanned , requireSASL bool
2019-05-13 02:57:34 +02:00
var banMsg string
2020-05-19 15:37:06 +02:00
realIP := utils . AddrToIP ( wConn . RemoteAddr ( ) )
2020-05-05 04:29:10 +02:00
var proxiedIP net . IP
2020-05-19 15:37:06 +02:00
if wConn . Config . Tor {
2020-05-05 04:29:10 +02:00
// cover up details of the tor proxying infrastructure (not a user privacy concern,
// but a hardening measure):
proxiedIP = utils . IPv4LoopbackAddress
2019-05-13 02:57:34 +02:00
isBanned , banMsg = server . checkTorLimits ( )
} else {
2020-05-05 04:29:10 +02:00
ipToCheck := realIP
2020-05-19 15:37:06 +02:00
if wConn . ProxiedIP != nil {
proxiedIP = wConn . ProxiedIP
2020-05-05 04:29:10 +02:00
ipToCheck = proxiedIP
2020-01-09 10:38:59 +01:00
}
2020-09-14 10:28:12 +02:00
// XXX only run the check script now if the IP cannot be replaced by PROXY or WEBIRC,
// otherwise we'll do it in ApplyProxiedIP.
checkScripts := proxiedIP != nil || ! utils . IPInNets ( realIP , config . Server . proxyAllowedFromNets )
isBanned , requireSASL , banMsg = server . checkBans ( config , ipToCheck , checkScripts )
2019-05-13 02:57:34 +02:00
}
if isBanned {
// this might not show up properly on some clients,
// but our objective here is just to close the connection out before it has a load impact on us
2020-05-05 23:20:50 +02:00
conn . WriteLine ( [ ] byte ( fmt . Sprintf ( errorMsg , banMsg ) ) )
2020-05-05 04:29:10 +02:00
conn . Close ( )
2019-05-13 02:57:34 +02:00
return
}
2020-05-11 00:45:32 +02:00
server . logger . Info ( "connect-ip" , fmt . Sprintf ( "Client connecting: real IP %v, proxied IP %v" , realIP , proxiedIP ) )
2019-05-13 02:57:34 +02:00
2019-05-12 09:12:50 +02:00
now := time . Now ( ) . UTC ( )
2019-03-07 08:31:46 +01:00
// give them 1k of grace over the limit:
2020-05-05 04:29:10 +02:00
socket := NewSocket ( conn , config . Server . MaxSendQBytes )
2012-12-09 21:51:50 +01:00
client := & Client {
2020-02-27 08:13:31 +01:00
lastActive : now ,
channels : make ( ChannelSet ) ,
ctime : now ,
2020-05-19 15:37:06 +02:00
isSTSOnly : wConn . Config . STSOnly ,
2020-02-27 08:13:31 +01:00
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 ,
} ,
2021-04-07 14:44:17 +02:00
server : server ,
accountName : "*" ,
nick : "*" , // * is used until actual nick is given
nickCasefolded : "*" ,
nickMaskString : "*" , // * is used until actual nick is given
realIP : realIP ,
proxiedIP : proxiedIP ,
requireSASL : requireSASL ,
nextSessionID : 1 ,
writerSemaphore : utils . NewSemaphore ( 1 ) ,
2012-12-09 21:51:50 +01:00
}
2020-09-14 14:11:56 +02:00
if requireSASL {
client . requireSASLMessage = banMsg
}
2020-05-19 13:57:44 +02:00
client . history . Initialize ( config . History . ClientLength , time . Duration ( config . History . AutoresizeWindow ) )
2019-04-12 06:08:46 +02:00
session := & Session {
client : client ,
socket : socket ,
capVersion : caps . Cap301 ,
capState : caps . NoneState ,
2019-05-08 10:11:54 +02:00
ctime : now ,
2020-02-27 08:13:31 +01:00
lastActive : now ,
2019-05-13 02:57:34 +02:00
realIP : realIP ,
2020-05-05 04:29:10 +02:00
proxiedIP : proxiedIP ,
2020-05-19 15:37:06 +02:00
isTor : wConn . Config . Tor ,
2020-12-06 05:06:23 +01:00
hideSTS : wConn . Config . Tor || wConn . Config . HideSTS ,
2019-04-12 06:08:46 +02:00
}
client . sessions = [ ] * Session { session }
2016-09-07 13:32:58 +02:00
2020-03-22 14:51:36 +01:00
session . resetFakelag ( )
2020-05-19 15:37:06 +02:00
if wConn . Secure {
2020-05-10 12:17:11 +02:00
client . SetMode ( modes . TLS , true )
}
2020-04-30 05:43:55 +02:00
2020-05-19 15:37:06 +02:00
if wConn . Config . TLSConfig != nil {
2016-09-07 13:32:58 +02:00
// error is not useful to us here anyways so we can ignore it
2020-09-23 08:23:35 +02:00
session . certfp , session . peerCerts , _ = utils . GetCertFP ( wConn . Conn , RegisterTimeout )
2016-06-28 17:09:07 +02:00
}
2019-02-26 03:50:43 +01:00
2020-05-05 04:29:10 +02:00
if session . isTor {
2019-05-08 10:11:54 +02:00
session . rawHostname = config . Server . TorListeners . Vhost
2019-12-17 21:10:23 +01:00
client . rawHostname = session . rawHostname
2019-02-26 03:50:43 +01:00
} else {
2020-05-05 04:29:10 +02:00
if config . Server . CheckIdent {
2020-05-19 15:37:06 +02:00
client . doIdentLookup ( wConn . Conn )
2016-06-30 11:28:34 +02:00
}
2019-02-26 03:50:43 +01:00
}
2020-08-07 23:30:42 +02:00
client . registrationTimer = time . AfterFunc ( RegisterTimeout , client . handleRegisterTimeout )
2019-07-01 15:21:38 +02:00
server . stats . Add ( )
2020-05-05 04:29:10 +02:00
client . run ( session )
2019-02-26 03:50:43 +01:00
}
2021-01-21 03:13:18 +01:00
func ( server * Server ) AddAlwaysOnClient ( account ClientAccount , channelToStatus map [ string ] alwaysOnChannelStatus , lastSeen map [ string ] time . Time , uModes modes . Modes , realname string ) {
2020-02-19 01:38:42 +01:00
now := time . Now ( ) . UTC ( )
config := server . Config ( )
2020-06-14 19:52:29 +02:00
if lastSeen == nil && account . Settings . AutoreplayMissed {
lastSeen = map [ string ] time . Time { "" : now }
2020-02-27 08:13:31 +01:00
}
2020-02-19 01:38:42 +01:00
2020-10-09 17:11:06 +02:00
rawHostname , cloakedHostname := server . name , ""
2020-10-08 22:33:27 +02:00
if config . Server . Cloaks . EnabledForAlwaysOn {
2020-10-09 17:11:06 +02:00
cloakedHostname = config . Server . Cloaks . ComputeAccountCloak ( account . Name )
2020-10-08 22:33:27 +02:00
}
2020-10-20 19:48:19 +02:00
username := "~u"
if config . Server . CoerceIdent != "" {
username = config . Server . CoerceIdent
}
2020-02-19 01:38:42 +01:00
client := & Client {
2020-03-02 09:06:57 +01:00
lastSeen : lastSeen ,
lastActive : now ,
2020-02-27 08:13:31 +01:00
channels : make ( ChannelSet ) ,
ctime : now ,
languages : server . Languages ( ) . Default ( ) ,
server : server ,
2020-02-19 01:38:42 +01:00
2020-10-20 19:48:19 +02:00
username : username ,
2020-10-09 17:11:06 +02:00
cloakedHostname : cloakedHostname ,
rawHostname : rawHostname ,
realIP : utils . IPv4LoopbackAddress ,
2020-02-19 01:38:42 +01:00
2020-02-27 08:13:31 +01:00
alwaysOn : true ,
2020-07-06 10:08:04 +02:00
realname : realname ,
2020-09-19 19:19:41 +02:00
nextSessionID : 1 ,
2021-04-07 14:44:17 +02:00
writerSemaphore : utils . NewSemaphore ( 1 ) ,
2020-02-19 01:38:42 +01:00
}
2021-01-15 12:50:35 +01:00
if client . checkAlwaysOnExpirationNoMutex ( config , true ) {
2020-12-21 11:11:50 +01:00
server . logger . Debug ( "accounts" , "always-on client not created due to expiration" , account . Name )
return
}
2020-02-19 01:38:42 +01:00
client . SetMode ( modes . TLS , true )
2020-05-19 20:38:56 +02:00
for _ , m := range uModes {
client . SetMode ( m , true )
}
2020-02-19 01:38:42 +01:00
client . history . Initialize ( 0 , 0 )
server . accounts . Login ( client , account )
client . resizeHistory ( config )
2020-10-07 00:04:29 +02:00
_ , err , _ := server . clients . SetNick ( client , nil , account . Name , false )
2020-02-19 01:38:42 +01:00
if err != nil {
server . logger . Error ( "internal" , "could not establish always-on client" , account . Name , err . Error ( ) )
return
} else {
server . logger . Debug ( "accounts" , "established always-on client" , account . Name )
}
// XXX set this last to avoid confusing SetNick:
client . registered = true
2021-01-21 03:13:18 +01:00
for chname , status := range channelToStatus {
2020-02-19 01:38:42 +01:00
// XXX we're using isSajoin=true, to make these joins succeed even without channel key
// this is *probably* ok as long as the persisted memberships are accurate
server . channels . Join ( client , chname , "" , true , nil )
2020-12-02 09:56:00 +01:00
if channel := server . channels . Get ( chname ) ; channel != nil {
2021-01-21 03:13:18 +01:00
channel . setMemberStatus ( client , status )
2020-12-02 09:56:00 +01:00
} else {
server . logger . Error ( "internal" , "could not create channel" , chname )
}
2020-02-19 01:38:42 +01:00
}
2020-05-19 20:12:20 +02:00
if persistenceEnabled ( config . Accounts . Multiclient . AutoAway , client . accountSettings . AutoAway ) {
2021-03-18 08:04:44 +01:00
client . setAutoAwayNoMutex ( config )
2020-05-19 20:12:20 +02:00
}
2020-02-19 01:38:42 +01:00
}
func ( client * Client ) resizeHistory ( config * Config ) {
2020-02-24 20:09:00 +01:00
status , _ := client . historyStatus ( config )
if status == HistoryEphemeral {
2020-05-19 13:57:44 +02:00
client . history . Resize ( config . History . ClientLength , time . Duration ( config . History . AutoresizeWindow ) )
2020-02-19 01:38:42 +01:00
} else {
client . history . Resize ( 0 , 0 )
}
}
2019-12-17 21:10:23 +01:00
// resolve an IP to an IRC-ready hostname, using reverse DNS, forward-confirming if necessary,
// and sending appropriate notices to the client
func ( client * Client ) lookupHostname ( session * Session , overwrite bool ) {
2020-02-19 01:38:42 +01:00
if session . isTor {
2019-12-17 21:10:23 +01:00
return
} // else: even if cloaking is enabled, look up the real hostname to show to operators
config := client . server . Config ( )
ip := session . realIP
if session . proxiedIP != nil {
ip = session . proxiedIP
}
ipString := ip . String ( )
var hostname , candidate string
if config . Server . lookupHostnames {
session . Notice ( "*** Looking up your hostname..." )
names , err := net . LookupAddr ( ipString )
if err == nil && 0 < len ( names ) {
candidate = strings . TrimSuffix ( names [ 0 ] , "." )
}
if utils . IsHostname ( candidate ) {
if config . Server . ForwardConfirmHostnames {
addrs , err := net . LookupHost ( candidate )
if err == nil {
for _ , addr := range addrs {
if addr == ipString {
hostname = candidate // successful forward confirmation
break
}
}
}
} else {
hostname = candidate
}
}
}
if hostname != "" {
session . Notice ( "*** Found your hostname" )
} else {
if config . Server . lookupHostnames {
session . Notice ( "*** Couldn't look up your hostname" )
}
hostname = utils . IPStringToHostname ( ipString )
}
session . rawHostname = hostname
2019-12-18 01:57:23 +01:00
cloakedHostname := config . Server . Cloaks . ComputeCloak ( ip )
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
2021-05-19 05:27:46 +02:00
// update the hostname if this is a new connection, but not if it's a reattach
2019-12-17 21:10:23 +01:00
if overwrite || client . rawHostname == "" {
client . rawHostname = hostname
client . cloakedHostname = cloakedHostname
client . updateNickMaskNoMutex ( )
}
}
2019-02-26 03:50:43 +01:00
func ( client * Client ) doIdentLookup ( conn net . Conn ) {
2020-05-05 04:29:10 +02:00
localTCPAddr , ok := conn . LocalAddr ( ) . ( * net . TCPAddr )
if ! ok {
2019-02-26 03:50:43 +01:00
return
}
2020-05-05 04:29:10 +02:00
serverPort := localTCPAddr . Port
remoteTCPAddr , ok := conn . RemoteAddr ( ) . ( * net . TCPAddr )
if ! ok {
2019-02-26 03:50:43 +01:00
return
}
2020-05-05 04:29:10 +02:00
clientPort := remoteTCPAddr . Port
2016-06-30 11:28:34 +02:00
2019-02-26 03:50:43 +01:00
client . Notice ( client . t ( "*** Looking up your username" ) )
2020-05-12 00:25:25 +02:00
resp , err := ident . Query ( remoteTCPAddr . IP . String ( ) , serverPort , clientPort , IdentTimeout )
2019-02-26 03:50:43 +01:00
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-05-23 02:25:57 +02:00
type AuthOutcome uint
const (
authSuccess AuthOutcome = iota
authFailPass
authFailTorSaslRequired
authFailSaslRequired
)
2020-09-14 10:28:12 +02:00
func ( client * Client ) isAuthorized ( server * Server , config * Config , session * Session , forceRequireSASL bool ) AuthOutcome {
2019-02-05 06:19:03 +01:00
saslSent := client . account != ""
2019-02-26 03:50:43 +01:00
// PASS requirement
2020-05-18 00:06:20 +02:00
if ( config . Server . passwordBytes != nil ) && session . passStatus != serverPassSuccessful && ! ( config . Accounts . SkipServerPassword && saslSent ) {
2019-05-23 02:25:57 +02:00
return authFailPass
2019-02-05 06:19:03 +01:00
}
2019-02-26 03:50:43 +01:00
// Tor connections may be required to authenticate with SASL
2020-12-11 11:04:56 +01:00
if session . isTor && ! saslSent && ( config . Server . TorListeners . RequireSasl || server . Defcon ( ) <= 4 ) {
2019-05-23 02:25:57 +02:00
return authFailTorSaslRequired
2019-02-26 03:50:43 +01:00
}
// finally, enforce require-sasl
2020-09-14 10:28:12 +02:00
if ! saslSent && ( forceRequireSASL || config . Accounts . RequireSasl . Enabled || server . Defcon ( ) <= 2 ) &&
2020-07-08 11:32:14 +02:00
! utils . IPInNets ( session . IP ( ) , config . Accounts . RequireSasl . exemptedNets ) {
2019-05-23 02:25:57 +02:00
return authFailSaslRequired
}
return authSuccess
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 ( )
2021-06-20 19:26:30 +02:00
return client . getIPNoMutex ( )
}
func ( client * Client ) getIPNoMutex ( ) net . IP {
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 {
2021-08-26 04:31:38 +02:00
return utils . IPStringToHostname ( client . IP ( ) . String ( ) )
2017-06-22 21:15:10 +02:00
}
2019-07-01 15:21:38 +02:00
// t returns the translated version of the given string, based on the languages configured by the client.
func ( client * Client ) t ( originalString string ) string {
languageManager := client . server . Config ( ) . languageManager
if ! languageManager . Enabled ( ) {
return originalString
}
return languageManager . Translate ( client . Languages ( ) , originalString )
}
2019-11-20 23:14:42 +01:00
// main client goroutine: read lines and execute the corresponding commands
// `proxyLine` is the PROXY-before-TLS line, if there was one
2020-05-05 04:29:10 +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 ( ) ) )
2019-05-23 01:07:12 +02:00
if client . server . Config ( ) . Debug . recoverFromErrors {
2017-10-26 11:15:55 +02:00
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-05-22 03:40:25 +02:00
client . destroy ( session )
2017-10-24 00:38:32 +02:00
} ( )
2019-04-12 06:08:46 +02:00
isReattach := client . Registered ( )
2019-05-09 00:14:49 +02:00
if isReattach {
2020-08-07 23:30:42 +02:00
client . Touch ( session )
2021-05-19 05:27:46 +02:00
client . playReattachMessages ( session )
2019-04-12 06:08:46 +02:00
}
2018-03-22 16:04:21 +01:00
2019-05-27 10:18:07 +02:00
firstLine := ! isReattach
2018-09-03 06:19:10 +02:00
2016-06-17 14:17:42 +02:00
for {
2020-06-22 20:54:43 +02:00
var invalidUtf8 bool
2020-05-05 04:29:10 +02:00
line , err := session . socket . Read ( )
2020-06-22 20:54:43 +02:00
if err == errInvalidUtf8 {
invalidUtf8 = true // handle as normal, including labeling
} else if err != nil {
2021-07-05 09:30:18 +02:00
client . server . logger . Debug ( "connect-ip" , "read error from client" , err . Error ( ) )
2021-01-15 12:19:13 +01:00
var quitMessage string
switch err {
2021-02-24 20:08:04 +01:00
case ircreader . ErrReadQ :
2021-01-15 12:19:13 +01:00
quitMessage = err . Error ( )
default :
quitMessage = "connection closed"
2018-03-18 02:32:12 +01:00
}
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-05-27 10:18:07 +02:00
if 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-05-23 00:35:24 +02:00
if client . registered {
2020-03-27 15:40:19 +01:00
touches := session . deferredFakelagCount + 1
session . deferredFakelagCount = 0
for i := 0 ; i < touches ; i ++ {
session . fakelag . Touch ( )
}
2019-05-23 00:35:24 +02:00
} else {
// DoS hardening, #505
2019-05-22 22:15:59 +02:00
session . registrationMessages ++
if client . server . Config ( ) . Limits . RegistrationMessages < session . registrationMessages {
client . Send ( nil , client . server . name , ERR_UNKNOWNERROR , "*" , client . t ( "You have sent too many registration messages" ) )
break
}
}
2020-08-03 18:51:04 +02:00
msg , err := ircmsg . ParseLineStrict ( line , true , MaxLineLen )
2017-01-18 22:56:33 +01:00
if err == ircmsg . ErrorLineIsEmpty {
continue
2021-03-05 04:29:34 +01:00
} else if err == ircmsg . ErrorTagsTooLong {
session . Send ( nil , client . server . name , ERR_INPUTTOOLONG , client . Nick ( ) , client . t ( "Input line contained excess tag data" ) )
continue
2021-08-03 03:49:42 +02:00
} else if err == ircmsg . ErrorBodyTooLong {
if ! client . server . Config ( ) . Server . Compatibility . allowTruncation {
session . Send ( nil , client . server . name , ERR_INPUTTOOLONG , client . Nick ( ) , client . t ( "Input line too long" ) )
continue
} // else: proceed with the truncated line
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 {
2020-05-08 11:44:40 +02:00
cmd = unknownCommand
2020-06-22 20:54:43 +02:00
} else if invalidUtf8 {
cmd = invalidUtf8Command
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
2020-05-05 04:29:10 +02:00
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-05-09 00:14:49 +02:00
func ( client * Client ) playReattachMessages ( session * Session ) {
client . server . playRegistrationBurst ( session )
2020-02-19 01:38:42 +01:00
hasHistoryCaps := session . HasHistoryCaps ( )
2019-04-12 06:08:46 +02:00
for _ , channel := range session . client . Channels ( ) {
channel . playJoinForSession ( session )
2020-02-19 01:38:42 +01:00
// clients should receive autoreplay-on-join lines, if applicable:
if hasHistoryCaps {
continue
}
2019-05-30 01:23:46 +02:00
// if they negotiated znc.in/playback or chathistory, they will receive nothing,
// because those caps disable autoreplay-on-join and they haven't sent the relevant
// *playback PRIVMSG or CHATHISTORY command yet
rb := NewResponseBuffer ( session )
channel . autoReplayHistory ( client , rb , "" )
rb . Send ( true )
2019-04-12 06:08:46 +02:00
}
2020-02-27 08:13:31 +01:00
if ! session . autoreplayMissedSince . IsZero ( ) && ! hasHistoryCaps {
2020-02-19 01:38:42 +01:00
rb := NewResponseBuffer ( session )
2021-05-29 00:07:54 +02:00
zncPlayPrivmsgsFromAll ( client , rb , time . Now ( ) . UTC ( ) , session . autoreplayMissedSince )
2020-02-19 01:38:42 +01:00
rb . Send ( true )
}
2020-02-27 08:13:31 +01:00
session . autoreplayMissedSince = time . Time { }
2019-04-12 06:08:46 +02:00
}
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
2020-02-27 08:13:31 +01:00
// Touch indicates that we received a line from the client (so the connection is healthy
2020-06-29 10:32:39 +02:00
// at this time, modulo network latency and fakelag).
func ( client * Client ) Touch ( session * Session ) {
2020-06-12 21:51:48 +02:00
var markDirty bool
2019-05-12 09:12:50 +02:00
now := time . Now ( ) . UTC ( )
2017-12-03 02:05:06 +01:00
client . stateMutex . Lock ( )
2020-12-21 11:11:50 +01:00
if client . registered {
client . updateIdleTimer ( session , now )
if client . alwaysOn {
client . setLastSeen ( now , session . deviceID )
if now . Sub ( client . lastSeenLastWrite ) > lastSeenWriteInterval {
markDirty = true
client . lastSeenLastWrite = now
}
2020-06-12 21:51:48 +02:00
}
}
client . stateMutex . Unlock ( )
if markDirty {
client . markDirty ( IncludeLastSeen )
}
}
func ( client * Client ) setLastSeen ( now time . Time , deviceID string ) {
2020-06-14 19:52:29 +02:00
if client . lastSeen == nil {
client . lastSeen = make ( map [ string ] time . Time )
}
2020-06-12 21:51:48 +02:00
client . lastSeen [ deviceID ] = now
// evict the least-recently-used entry if necessary
if maxDeviceIDsPerClient < len ( client . lastSeen ) {
var minLastSeen time . Time
var minClientId string
for deviceID , lastSeen := range client . lastSeen {
if minLastSeen . IsZero ( ) || lastSeen . Before ( minLastSeen ) {
minClientId , minLastSeen = deviceID , lastSeen
}
}
delete ( client . lastSeen , minClientId )
}
2014-02-18 22:25:21 +01:00
}
2014-02-14 03:39:33 +01:00
2020-08-07 23:30:42 +02:00
func ( client * Client ) updateIdleTimer ( session * Session , now time . Time ) {
session . lastTouch = now
session . pingSent = false
if session . idleTimer == nil {
pingTimeout := DefaultIdleTimeout
if session . isTor {
pingTimeout = TorIdleTimeout
}
session . idleTimer = time . AfterFunc ( pingTimeout , session . handleIdleTimeout )
}
}
func ( session * Session ) handleIdleTimeout ( ) {
totalTimeout := DefaultTotalTimeout
pingTimeout := DefaultIdleTimeout
if session . isTor {
pingTimeout = TorIdleTimeout
}
session . client . stateMutex . Lock ( )
now := time . Now ( )
timeUntilDestroy := session . lastTouch . Add ( totalTimeout ) . Sub ( now )
timeUntilPing := session . lastTouch . Add ( pingTimeout ) . Sub ( now )
shouldDestroy := session . pingSent && timeUntilDestroy <= 0
2020-08-09 04:39:28 +02:00
// XXX this should really be time <= 0, but let's do some hacky timer coalescing:
// a typical idling client will do nothing other than respond immediately to our pings,
// so we'll PING at t=0, they'll respond at t=0.05, then we'll wake up at t=90 and find
// that we need to PING again at t=90.05. Rather than wake up again, just send it now:
shouldSendPing := ! session . pingSent && timeUntilPing <= PingCoalesceThreshold
2020-08-07 23:30:42 +02:00
if ! shouldDestroy {
if shouldSendPing {
session . pingSent = true
}
// check in again at the minimum of these 3 possible intervals:
// 1. the ping timeout (assuming we PING and they reply immediately with PONG)
// 2. the next time we would send PING (if they don't send any more lines)
// 3. the next time we would destroy (if they don't send any more lines)
nextTimeout := pingTimeout
2020-08-09 04:39:28 +02:00
if PingCoalesceThreshold < timeUntilPing && timeUntilPing < nextTimeout {
2020-08-07 23:30:42 +02:00
nextTimeout = timeUntilPing
}
if 0 < timeUntilDestroy && timeUntilDestroy < nextTimeout {
nextTimeout = timeUntilDestroy
}
session . idleTimer . Stop ( )
session . idleTimer . Reset ( nextTimeout )
}
session . client . stateMutex . Unlock ( )
if shouldDestroy {
session . client . Quit ( fmt . Sprintf ( "Ping timeout: %v" , totalTimeout ) , session )
session . client . destroy ( session )
} else if shouldSendPing {
session . Ping ( )
}
}
func ( session * Session ) stopIdleTimer ( ) {
session . client . stateMutex . Lock ( )
defer session . client . stateMutex . Unlock ( )
if session . idleTimer != nil {
session . idleTimer . Stop ( )
}
}
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
}
2021-04-06 06:46:07 +02:00
func ( client * Client ) replayPrivmsgHistory ( rb * ResponseBuffer , items [ ] history . Item , target string ) {
2019-05-07 05:17:57 +02:00
var batchID string
2019-05-19 08:14:36 +02:00
details := client . Details ( )
nick := details . nick
2020-02-21 05:47:13 +01:00
if target == "" {
target = nick
2019-05-07 05:17:57 +02:00
}
2020-02-21 05:47:13 +01:00
batchID = rb . StartNestedHistoryBatch ( target )
2019-05-07 05:17:57 +02:00
2020-12-14 14:24:38 +01:00
isSelfMessage := func ( item * history . Item ) bool {
// XXX: Params[0] is the message target. if the source of this message is an in-memory
// buffer, then it's "" for an incoming message and the recipient's nick for an outgoing
// message. if the source of the message is mysql, then mysql only sees one copy of the
// message, and it's the version with the recipient's nick filled in. so this is an
// incoming message if Params[0] (the recipient's nick) equals the client's nick:
return item . Params [ 0 ] != "" && item . Params [ 0 ] != nick
}
2020-11-30 01:06:12 +01:00
hasEventPlayback := rb . session . capabilities . Has ( caps . EventPlayback )
hasTags := rb . session . capabilities . Has ( caps . MessageTags )
2019-02-04 11:18:17 +01:00
for _ , item := range items {
var command string
switch item . Type {
2020-11-30 04:12:06 +01:00
case history . Invite :
2020-12-14 14:24:38 +01:00
if isSelfMessage ( & item ) {
continue
}
2020-11-30 04:12:06 +01:00
if hasEventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "INVITE" , nick , item . Message . Message )
2020-11-30 04:12:06 +01:00
} else {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , utils . MungeSecretToken ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , fmt . Sprintf ( client . t ( "%[1]s invited you to channel %[2]s" ) , NUHToNick ( item . Nick ) , item . Message . Message ) )
2020-11-30 04:12:06 +01:00
}
continue
2019-02-04 11:18:17 +01:00
case history . Privmsg :
command = "PRIVMSG"
case history . Notice :
command = "NOTICE"
2019-05-07 05:17:57 +02:00
case history . Tagmsg :
2020-11-30 01:06:12 +01:00
if hasEventPlayback && hasTags {
2019-05-07 05:17:57 +02:00
command = "TAGMSG"
} else {
continue
}
2019-02-04 11:18:17 +01:00
default :
continue
}
2019-03-07 08:31:46 +01:00
var tags map [ string ] string
2020-11-30 01:06:12 +01:00
if hasTags {
2019-05-07 05:17:57 +02:00
tags = item . Tags
2019-02-04 11:18:17 +01:00
}
2020-12-14 14:24:38 +01:00
if ! isSelfMessage ( & item ) {
2021-03-17 19:36:52 +01:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , tags , command , nick , item . Message )
2019-05-19 08:14:36 +02:00
} else {
// this message was sent *from* the client to another nick; the target is item.Params[0]
2020-02-19 01:38:42 +01:00
// substitute client's current nickmask in case client changed nick
2021-03-17 19:36:52 +01:00
rb . AddSplitMessageFromClient ( details . nickMask , item . AccountName , item . IsBot , tags , command , item . Params [ 0 ] , item . Message )
2019-05-19 08:14:36 +02:00
}
2019-02-04 11:18:17 +01:00
}
2019-05-07 05:17:57 +02:00
rb . EndNestedBatch ( batchID )
2019-02-04 11:18:17 +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 ( )
2020-02-27 08:13:31 +01:00
return time . Since ( client . lastActive )
2014-02-18 00:25:32 +01:00
}
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 ( ) )
}
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 {
2020-09-07 11:59:31 +02:00
config := client . server . Config ( )
limit := config . Limits . IdentLen
2019-02-05 08:40:49 +01:00
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
}
2020-10-20 19:48:19 +02:00
if config . Server . CoerceIdent != "" {
username = config . Server . CoerceIdent
2020-09-07 11:59:31 +02:00
} else if ! fromIdent {
2019-02-05 08:40:49 +01:00
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 {
2020-03-18 10:42:52 +01:00
if ! oper . Class . Capabilities . Has ( 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 ) {
2020-02-19 01:38:42 +01:00
return "+" + client . modes . 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.
2020-07-17 10:53:30 +02:00
func ( client * Client ) Friends ( capabs ... caps . Capability ) ( result map [ * Session ] empty ) {
result = make ( map [ * Session ] empty )
2016-10-26 16:44:36 +02:00
2019-04-12 06:08:46 +02:00
// look at the client's own sessions
2020-07-17 10:53:30 +02:00
addFriendsToSet ( result , client , capabs ... )
2016-10-26 16:44:36 +02:00
2017-10-23 01:50:16 +02:00
for _ , channel := range client . Channels ( ) {
2020-10-02 14:13:52 +02:00
for _ , member := range channel . auditoriumFriends ( client ) {
2020-07-17 10:53:30 +02:00
addFriendsToSet ( result , member , capabs ... )
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
}
2020-07-17 10:53:30 +02:00
// helper for Friends
func addFriendsToSet ( set map [ * Session ] empty , client * Client , capabs ... caps . Capability ) {
client . stateMutex . RLock ( )
defer client . stateMutex . RUnlock ( )
for _ , session := range client . sessions {
if session . capabilities . HasAll ( capabs ... ) {
set [ session ] = empty { }
}
}
}
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 ) {
2020-06-03 00:57:28 +02:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2018-04-19 08:48:19 +02:00
for fClient := range client . Friends ( caps . ChgHost ) {
2021-03-17 19:36:52 +01:00
fClient . sendFromClientInternal ( false , time . Time { } , "" , oldNickMask , details . accountName , isBot , nil , "CHGHOST" , details . 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
2020-10-09 14:03:26 +02:00
} else if client . oper != nil && ! client . oper . Hidden {
2018-04-19 08:48:19 +02:00
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
}
2020-08-06 09:16:58 +02:00
// SetNick gives the client a nickname and marks it as registered, if necessary
func ( client * Client ) SetNick ( nick , nickCasefolded , skeleton string ) ( success bool ) {
2017-10-04 06:57:03 +02:00
client . stateMutex . Lock ( )
2019-01-31 00:59:49 +01:00
defer client . stateMutex . Unlock ( )
2020-08-06 09:16:58 +02:00
if client . destroyed {
return false
} else if ! client . registered {
// XXX test this before setting it to avoid annoying the race detector
client . registered = true
2020-08-07 23:30:42 +02:00
if client . registrationTimer != nil {
client . registrationTimer . Stop ( )
client . registrationTimer = nil
}
2020-08-06 09:16:58 +02:00
}
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 ( )
2020-08-06 09:16:58 +02:00
return true
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 ( ) {
2020-02-06 23:43:54 +01:00
if client . nick == "*" {
return // pre-registration, don't bother generating the hostname
}
2018-04-19 08:48:19 +02:00
client . hostname = client . getVHostNoMutex ( )
if client . hostname == "" {
2019-05-12 08:17:57 +02:00
client . hostname = client . cloakedHostname
if client . hostname == "" {
client . hostname = client . rawHostname
}
2016-10-23 03:28:31 +02:00
}
2019-12-18 13:01:26 +01:00
cfhostname := strings . ToLower ( client . hostname )
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
2019-05-12 08:17:57 +02:00
cloakedHostname := client . cloakedHostname
2018-04-19 08:48:19 +02:00
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-12-18 13:01:26 +01:00
cfvhost := strings . ToLower ( vhost )
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
2019-12-18 13:01:26 +01:00
cfrawhost := strings . ToLower ( rawHostname )
rawhostmask = fmt . Sprintf ( "%s!%s@%s" , nick , username , cfrawhost )
masks = append ( masks , rawhostmask )
2017-01-11 13:38:16 +01:00
2019-05-12 08:17:57 +02:00
if cloakedHostname != "" {
masks = append ( masks , fmt . Sprintf ( "%s!%s@%s" , nick , username , cloakedHostname ) )
}
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
}
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 )
2020-08-03 18:51:04 +02:00
finalData , _ = quitMsg . LineBytesStrict ( false , MaxLineLen )
2019-04-12 06:08:46 +02:00
}
2017-10-11 02:49:29 +02:00
2019-04-12 06:08:46 +02:00
errorMsg := ircmsg . MakeMessage ( nil , "" , "ERROR" , message )
2020-08-03 18:51:04 +02:00
errorMsgBytes , _ := errorMsg . LineBytesStrict ( false , MaxLineLen )
2019-04-12 06:08:46 +02:00
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 {
2020-12-21 11:11:50 +01:00
if session . setQuitMessage ( message ) {
2019-04-12 06:08:46 +02:00
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.
2019-05-22 03:40:25 +02:00
func ( client * Client ) destroy ( session * Session ) {
2020-05-19 20:12:20 +02:00
config := client . server . Config ( )
2019-04-12 06:08:46 +02:00
var sessionsToDestroy [ ] * Session
2020-06-18 09:38:00 +02:00
var saveLastSeen bool
2020-12-21 11:11:50 +01:00
var quitMessage string
2019-04-12 06:08:46 +02:00
2018-11-26 11:23:27 +01:00
client . stateMutex . Lock ( )
2020-06-18 09:38:00 +02:00
2019-05-09 00:14:49 +02:00
details := client . detailsNoMutex ( )
wasReattach := session != nil && session . client != client
2019-04-12 06:08:46 +02:00
sessionRemoved := false
2020-02-19 01:38:42 +01:00
registered := client . registered
2020-07-26 21:51:33 +02:00
// XXX a temporary (reattaching) client can be marked alwaysOn when it logs in,
// but then the session attaches to another client and we need to clean it up here
alwaysOn := registered && client . alwaysOn
2020-12-21 11:11:50 +01:00
// if we hit always-on-expiration, confirm the expiration and then proceed as though
// always-on is disabled:
2021-01-15 12:50:35 +01:00
if alwaysOn && session == nil && client . checkAlwaysOnExpirationNoMutex ( config , false ) {
2020-12-21 11:11:50 +01:00
quitMessage = "Timed out due to inactivity"
alwaysOn = false
client . alwaysOn = false
}
2020-06-18 09:38:00 +02:00
2019-04-12 06:08:46 +02:00
var remainingSessions int
if session == nil {
sessionsToDestroy = client . sessions
client . sessions = nil
remainingSessions = 0
} else {
sessionRemoved , remainingSessions = client . removeSession ( session )
if sessionRemoved {
sessionsToDestroy = [ ] * Session { session }
}
}
2019-05-27 11:52:40 +02:00
2020-06-18 09:38:00 +02:00
// save last seen if applicable:
if alwaysOn {
if client . accountSettings . AutoreplayMissed {
saveLastSeen = true
} else {
for _ , session := range sessionsToDestroy {
if session . deviceID != "" {
saveLastSeen = true
break
}
}
}
}
2019-05-27 11:52:40 +02:00
// should we destroy the whole client this time?
2021-05-19 05:27:46 +02:00
shouldDestroy := ! client . destroyed && remainingSessions == 0 && ! alwaysOn
2020-03-06 10:21:21 +01:00
// decrement stats on a true destroy, or for the removal of the last connected session
// of an always-on client
shouldDecrement := shouldDestroy || ( alwaysOn && len ( sessionsToDestroy ) != 0 && len ( client . sessions ) == 0 )
2019-05-27 11:52:40 +02:00
if shouldDestroy {
// if it's our job to destroy it, don't let anyone else try
client . destroyed = true
}
2020-02-27 08:13:31 +01:00
if saveLastSeen {
client . dirtyBits |= IncludeLastSeen
2020-02-19 01:38:42 +01:00
}
2020-05-19 20:12:20 +02:00
2021-03-18 07:53:18 +01:00
becameAutoAway := false
2020-05-19 20:12:20 +02:00
var awayMessage string
2021-03-18 07:53:18 +01:00
if alwaysOn && persistenceEnabled ( config . Accounts . Multiclient . AutoAway , client . accountSettings . AutoAway ) {
wasAway := client . awayMessage != ""
client . setAutoAwayNoMutex ( config )
awayMessage = client . awayMessage
becameAutoAway = ! wasAway && awayMessage != ""
2020-05-19 20:12:20 +02:00
}
2020-08-07 23:30:42 +02:00
if client . registrationTimer != nil {
// unconditionally stop; if the client is still unregistered it must be destroyed
client . registrationTimer . Stop ( )
}
2018-11-26 11:23:27 +01:00
client . stateMutex . Unlock ( )
2020-02-27 08:13:31 +01:00
// XXX there is no particular reason to persist this state here rather than
// any other place: it would be correct to persist it after every `Touch`. However,
// I'm not comfortable introducing that many database writes, and I don't want to
// design a throttle.
if saveLastSeen {
2020-02-20 08:33:49 +01:00
client . wakeWriter ( )
}
2019-05-09 00:14:49 +02:00
// destroy all applicable sessions:
2019-04-12 06:08:46 +02:00
for _ , session := range sessionsToDestroy {
if session . client != client {
// session has been attached to a new client; do not destroy it
continue
}
2020-08-07 23:30:42 +02:00
session . stopIdleTimer ( )
2019-04-12 06:08:46 +02:00
// send quit/error message to client if they haven't been sent already
client . Quit ( "" , session )
2020-12-21 11:11:50 +01:00
quitMessage = session . quitMessage // doesn't need synch, we already detached
2019-05-09 00:14:49 +02:00
session . socket . Close ( )
2020-06-22 05:51:31 +02:00
// clean up monitor state
client . server . monitorManager . RemoveAll ( session )
2019-05-09 00:14:49 +02:00
// remove from connection limits
var source string
2020-02-19 01:38:42 +01:00
if session . isTor {
2019-05-09 00:14:49 +02:00
client . server . torLimiter . RemoveClient ( )
source = "tor"
} else {
ip := session . realIP
if session . proxiedIP != nil {
ip = session . proxiedIP
}
2020-12-09 04:01:23 +01:00
client . server . connectionLimiter . RemoveClient ( flatip . FromNetIP ( ip ) )
2019-05-09 00:14:49 +02:00
source = ip . String ( )
}
2021-07-03 01:11:42 +02:00
if ! shouldDestroy {
client . server . snomasks . Send ( sno . LocalDisconnects , fmt . Sprintf ( ircfmt . Unescape ( "Client session disconnected for [a:%s] [h:%s] [ip:%s]" ) , details . accountName , session . rawHostname , source ) )
}
2020-04-12 19:58:35 +02:00
client . server . logger . Info ( "connect-ip" , fmt . Sprintf ( "disconnecting session of %s from %s" , details . nick , source ) )
2019-04-12 06:08:46 +02:00
}
2020-02-19 01:38:42 +01:00
// decrement stats if we have no more sessions, even if the client will not be destroyed
2020-03-06 10:21:21 +01:00
if shouldDecrement {
2020-02-19 01:38:42 +01:00
invisible := client . HasMode ( modes . Invisible )
2021-02-07 04:45:34 +01:00
operator := client . HasMode ( modes . Operator )
2020-02-19 01:38:42 +01:00
client . server . stats . Remove ( registered , invisible , operator )
}
2021-03-18 07:53:18 +01:00
if becameAutoAway {
2020-05-19 20:12:20 +02:00
dispatchAwayNotify ( client , true , awayMessage )
}
2019-05-27 11:52:40 +02:00
if ! shouldDestroy {
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
2020-12-21 11:11:50 +01:00
var quitItem history . Item
2020-02-19 01:38:42 +01:00
var channels [ ] * Channel
2020-02-20 07:45:17 +01:00
// use a defer here to avoid writing to mysql while holding the destroy semaphore:
2020-02-19 01:38:42 +01:00
defer func ( ) {
for _ , channel := range channels {
2020-05-12 18:05:40 +02:00
channel . AddHistoryItem ( quitItem , details . account )
2020-02-19 01:38:42 +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 ( )
2019-05-22 03:40:25 +02:00
if ! wasReattach {
2019-05-07 05:17:57 +02:00
client . server . logger . Debug ( "quit" , fmt . Sprintf ( "%s is no longer on the server" , details . nick ) )
2018-01-21 02:23:33 +01:00
}
2017-03-06 13:11:10 +01:00
2019-05-22 03:40:25 +02:00
if registered {
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-16 12:14:56 +02:00
// alert monitors
2019-05-09 00:14:49 +02:00
if registered {
2020-05-28 23:55:53 +02:00
client . server . monitorManager . AlertAbout ( details . nick , details . nickCasefolded , false )
2019-05-09 00:14:49 +02:00
}
2016-10-16 12:14:56 +02:00
2016-06-17 14:17:42 +02:00
// clean up channels
2019-05-09 00:14:49 +02:00
// (note that if this is a reattach, client has no channels and therefore no friends)
2018-04-25 02:23:01 +02:00
friends := make ( ClientSet )
2020-02-19 01:38:42 +01:00
channels = client . Channels ( )
for _ , channel := range channels {
2020-10-02 14:13:52 +02:00
for _ , member := range channel . auditoriumFriends ( client ) {
2017-10-23 01:50:16 +02:00
friends . Add ( member )
}
2020-10-02 14:13:52 +02:00
channel . Quit ( client )
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
2019-05-22 03:40:25 +02:00
client . server . clients . Remove ( client )
2016-06-17 14:17:42 +02:00
// clean up self
2018-02-11 11:30:40 +01:00
client . server . accounts . Logout ( client )
2019-05-27 10:18:07 +02:00
if quitMessage == "" {
quitMessage = "Exited"
}
2020-12-21 11:11:50 +01:00
splitQuitMessage := utils . MakeMessage ( quitMessage )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2020-12-21 11:11:50 +01:00
quitItem = history . Item {
Type : history . Quit ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : splitQuitMessage ,
2021-03-17 19:36:52 +01:00
IsBot : isBot ,
2020-12-21 11:11:50 +01:00
}
2020-11-27 06:13:47 +01:00
var cache MessageCache
2021-03-17 19:36:52 +01:00
cache . Initialize ( client . server , splitQuitMessage . Time , splitQuitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "QUIT" , quitMessage )
2019-05-27 10:18:07 +02:00
for friend := range friends {
2020-11-27 06:13:47 +01:00
for _ , session := range friend . Sessions ( ) {
cache . Send ( session )
}
2016-11-29 12:06:01 +01:00
}
2019-05-22 03:40:25 +02:00
2020-07-10 23:09:02 +02:00
if registered {
2019-05-22 03:40:25 +02:00
client . server . snomasks . Send ( sno . LocalQuits , fmt . Sprintf ( ircfmt . Unescape ( "%s$r exited the network" ) , details . 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.
2021-03-17 19:36:52 +01:00
func ( session * Session ) sendSplitMsgFromClientInternal ( blocking bool , nickmask , accountName string , isBot bool , tags map [ string ] string , command , target string , message utils . SplitMessage ) {
2020-01-19 05:47:05 +01:00
if message . Is512 ( ) {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( blocking , message . Time , message . Msgid , nickmask , accountName , isBot , tags , command , target , message . Message )
2017-01-14 06:28:50 +01:00
} else {
2020-02-19 01:38:42 +01:00
if session . capabilities . Has ( caps . Multiline ) {
2021-03-17 19:36:52 +01:00
for _ , msg := range composeMultilineBatch ( session . generateBatchID ( ) , nickmask , accountName , isBot , tags , command , target , message ) {
2019-12-23 21:26:37 +01:00
session . SendRawMessage ( msg , blocking )
}
} else {
2020-05-14 18:58:49 +02:00
msgidSent := false // send msgid on the first nonblank line
for _ , messagePair := range message . Split {
if len ( messagePair . Message ) == 0 {
continue
}
2020-01-19 05:47:05 +01:00
var msgid string
2020-05-14 18:58:49 +02:00
if ! msgidSent {
msgidSent = true
2020-01-19 05:47:05 +01:00
msgid = message . Msgid
}
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( blocking , message . Time , msgid , nickmask , accountName , isBot , tags , command , target , messagePair . Message )
2019-12-23 21:26:37 +01:00
}
2017-01-14 06:28:50 +01:00
}
}
}
2021-03-17 19:36:52 +01:00
func ( session * Session ) sendFromClientInternal ( blocking bool , serverTime time . Time , msgid string , nickmask , accountName string , isBot bool , 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-05-15 07:30:21 +02:00
msg . SetTag ( "msgid" , msgid )
2019-03-07 08:31:46 +01:00
}
// attach server-time
2019-06-13 08:24:14 +02:00
session . setTimeTag ( & msg , serverTime )
2021-03-17 19:36:52 +01:00
// attach bot tag
if isBot && session . capabilities . Has ( caps . MessageTags ) {
msg . SetTag ( caps . BotTagName , "" )
}
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
}
2021-03-17 19:36:52 +01:00
func composeMultilineBatch ( batchID , fromNickMask , fromAccount string , isBot bool , tags map [ string ] string , command , target string , message utils . SplitMessage ) ( result [ ] ircmsg . Message ) {
2020-01-03 15:46:55 +01:00
batchStart := ircmsg . MakeMessage ( tags , fromNickMask , "BATCH" , "+" + batchID , caps . MultilineBatchType , target )
2019-12-23 21:26:37 +01:00
batchStart . SetTag ( "time" , message . Time . Format ( IRCv3TimestampFormat ) )
batchStart . SetTag ( "msgid" , message . Msgid )
2020-11-27 06:13:47 +01:00
if fromAccount != "*" {
2019-12-23 21:26:37 +01:00
batchStart . SetTag ( "account" , fromAccount )
}
2021-03-17 19:36:52 +01:00
if isBot {
batchStart . SetTag ( caps . BotTagName , "" )
}
2019-12-23 21:26:37 +01:00
result = append ( result , batchStart )
2020-01-19 05:47:05 +01:00
for _ , msg := range message . Split {
2019-12-23 21:26:37 +01:00
message := ircmsg . MakeMessage ( nil , fromNickMask , command , target , msg . Message )
message . SetTag ( "batch" , batchID )
if msg . Concat {
message . SetTag ( caps . MultilineConcatTag , "" )
}
result = append ( result , message )
}
result = append ( result , ircmsg . MakeMessage ( nil , fromNickMask , "BATCH" , "-" + batchID ) )
return
}
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 ,
2020-03-11 23:57:42 +01:00
// mirc's handling of RPL_NAMREPLY is broken:
// https://forums.mirc.com/ubbthreads.php/topics/266939/re-nick-list
RPL_NAMREPLY : 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.
2021-03-11 02:07:43 +01:00
func ( session * Session ) SendRawMessage ( message ircmsg . Message , 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
2019-05-09 20:18:30 +02:00
config := session . client . server . Config ( )
2020-03-11 23:57:25 +01:00
if config . Server . Compatibility . forceTrailing && commandsThatMustUseTrailing [ message . Command ] {
message . ForceTrailing ( )
2017-01-20 14:51:36 +01:00
}
2017-10-08 03:05:05 +02:00
// assemble message
2020-08-03 18:51:04 +02:00
line , err := message . LineBytesStrict ( false , MaxLineLen )
2021-03-05 04:29:34 +01:00
if ! ( err == nil || err == ircmsg . ErrorBodyTooLong ) {
2020-08-10 23:33:24 +02:00
errorParams := [ ] string { "Error assembling message for sending" , err . Error ( ) , message . Command }
errorParams = append ( errorParams , message . Params ... )
session . client . server . logger . Error ( "internal" , errorParams ... )
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
2020-11-27 06:13:47 +01:00
return session . sendBytes ( line , blocking )
}
func ( session * Session ) sendBytes ( line [ ] byte , blocking bool ) ( err error ) {
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 {
2020-10-05 13:35:18 +02:00
err = session . socket . BlockingWrite ( line )
2018-11-26 11:23:27 +01:00
} else {
2020-10-05 13:35:18 +02:00
err = session . socket . Write ( line )
2018-11-26 11:23:27 +01:00
}
2020-10-05 13:35:18 +02:00
if err != nil {
session . client . server . logger . Info ( "quit" , "send error to client" , fmt . Sprintf ( "%s [%d]" , session . client . Nick ( ) , session . sessionID ) , err . Error ( ) )
}
return err
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-06-13 08:24:14 +02:00
session . setTimeTag ( & msg , time . Time { } )
return session . SendRawMessage ( msg , false )
}
2021-03-11 02:07:43 +01:00
func ( session * Session ) setTimeTag ( msg * ircmsg . Message , serverTime time . Time ) {
2019-04-12 06:08:46 +02:00
if session . capabilities . Has ( caps . ServerTime ) && ! msg . HasTag ( "time" ) {
2019-06-13 08:24:14 +02:00
if serverTime . IsZero ( ) {
serverTime = time . Now ( )
}
msg . SetTag ( "time" , serverTime . UTC ( ) . Format ( IRCv3TimestampFormat ) )
2019-03-07 08:31:46 +01:00
}
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
2019-12-17 21:10:23 +01:00
func ( session * Session ) Notice ( text string ) {
session . Send ( nil , session . client . server . name , "NOTICE" , session . client . Nick ( ) , text )
}
2020-03-02 07:22:00 +01:00
// `simulated` is for the fake join of an always-on client
// (we just read the channel name from the database, there's no need to write it back)
2020-07-01 01:24:56 +02:00
func ( client * Client ) addChannel ( channel * Channel , simulated bool ) ( err error ) {
config := client . server . Config ( )
2017-10-23 01:50:16 +02:00
client . stateMutex . Lock ( )
2020-02-19 01:38:42 +01:00
alwaysOn := client . alwaysOn
2020-07-01 01:24:56 +02:00
if client . destroyed {
err = errClientDestroyed
} else if client . oper == nil && len ( client . channels ) >= config . Channels . MaxChannelsPerClient {
err = errTooManyChannels
} else {
client . channels [ channel ] = empty { } // success
}
2017-10-23 01:50:16 +02:00
client . stateMutex . Unlock ( )
2020-02-19 01:38:42 +01:00
2020-07-01 01:24:56 +02:00
if err == nil && alwaysOn && ! simulated {
2020-02-19 01:38:42 +01:00
client . markDirty ( IncludeChannels )
}
2020-07-01 01:24:56 +02:00
return
2017-10-23 01:50:16 +02:00
}
func ( client * Client ) removeChannel ( channel * Channel ) {
client . stateMutex . Lock ( )
delete ( client . channels , channel )
2020-02-19 01:38:42 +01:00
alwaysOn := client . alwaysOn
2017-10-23 01:50:16 +02:00
client . stateMutex . Unlock ( )
2020-02-19 01:38:42 +01:00
if alwaysOn {
client . markDirty ( IncludeChannels )
}
2017-10-23 01:50:16 +02:00
}
2018-11-26 11:23:27 +01:00
2020-10-26 01:40:41 +01:00
type channelInvite struct {
channelCreatedAt time . Time
invitedAt time . Time
}
2018-12-23 19:25:02 +01:00
// Records that the client has been invited to join an invite-only channel
2020-10-26 01:40:41 +01:00
func ( client * Client ) Invite ( casefoldedChannel string , channelCreatedAt time . Time ) {
now := time . Now ( ) . UTC ( )
2018-12-23 19:25:02 +01:00
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
if client . invitedTo == nil {
2020-10-26 01:40:41 +01:00
client . invitedTo = make ( map [ string ] channelInvite )
}
client . invitedTo [ casefoldedChannel ] = channelInvite {
channelCreatedAt : channelCreatedAt ,
invitedAt : now ,
2018-12-23 19:25:02 +01:00
}
2020-10-26 01:40:41 +01:00
return
2018-12-23 19:25:02 +01:00
}
2020-10-26 03:16:19 +01:00
func ( client * Client ) Uninvite ( casefoldedChannel string ) {
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
delete ( client . invitedTo , casefoldedChannel )
}
2018-12-23 19:25:02 +01:00
// Checks that the client was invited to join a given channel
2020-10-26 01:40:41 +01:00
func ( client * Client ) CheckInvited ( casefoldedChannel string , createdTime time . Time ) ( invited bool ) {
config := client . server . Config ( )
expTime := time . Duration ( config . Channels . InviteExpiration )
now := time . Now ( ) . UTC ( )
2018-12-23 19:25:02 +01:00
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
2020-10-26 01:40:41 +01:00
curInvite , ok := client . invitedTo [ casefoldedChannel ]
if ok {
// joining an invited channel "uses up" your invite, so you can't rejoin on kick
delete ( client . invitedTo , casefoldedChannel )
}
invited = ok && ( expTime == time . Duration ( 0 ) || now . Sub ( curInvite . invitedAt ) < expTime ) &&
createdTime . Equal ( curInvite . channelCreatedAt )
2018-12-23 19:25:02 +01:00
return
}
2019-12-19 12:33:43 +01:00
// Implements auto-oper by certfp (scans for an auto-eligible operator block that matches
// the client's cert, then applies it).
func ( client * Client ) attemptAutoOper ( session * Session ) {
2020-02-19 03:42:27 +01:00
if session . certfp == "" || client . HasMode ( modes . Operator ) {
2019-12-19 12:33:43 +01:00
return
}
for _ , oper := range client . server . Config ( ) . operators {
2020-06-21 21:46:08 +02:00
if oper . Auto && oper . Pass == nil && oper . Certfp != "" && oper . Certfp == session . certfp {
2019-12-19 12:33:43 +01:00
rb := NewResponseBuffer ( session )
applyOper ( client , oper , rb )
rb . Send ( true )
2019-12-29 17:59:49 +01:00
return
2019-12-19 12:33:43 +01:00
}
}
}
2020-02-19 01:38:42 +01:00
2020-06-12 21:51:48 +02:00
func ( client * Client ) checkLoginThrottle ( ) ( throttled bool , remainingTime time . Duration ) {
client . stateMutex . Lock ( )
defer client . stateMutex . Unlock ( )
return client . loginThrottle . Touch ( )
}
2020-02-24 20:09:00 +01:00
func ( client * Client ) historyStatus ( config * Config ) ( status HistoryStatus , target string ) {
2020-02-19 01:38:42 +01:00
if ! config . History . Enabled {
2020-02-24 20:09:00 +01:00
return HistoryDisabled , ""
2020-02-19 01:38:42 +01:00
}
client . stateMutex . RLock ( )
2020-02-28 11:41:08 +01:00
target = client . account
2020-02-19 01:38:42 +01:00
historyStatus := client . accountSettings . DMHistory
client . stateMutex . RUnlock ( )
2020-02-28 11:41:08 +01:00
if target == "" {
2020-02-24 20:09:00 +01:00
return HistoryEphemeral , ""
2020-02-19 01:38:42 +01:00
}
2020-02-28 11:41:08 +01:00
status = historyEnabled ( config . History . Persistent . DirectMessages , historyStatus )
if status != HistoryPersistent {
target = ""
}
return
2020-02-19 01:38:42 +01:00
}
2020-11-30 04:12:06 +01:00
func ( client * Client ) addHistoryItem ( target * Client , item history . Item , details , tDetails * ClientDetails , config * Config ) ( err error ) {
if ! itemIsStorable ( & item , config ) {
return
}
item . Nick = details . nickMask
item . AccountName = details . accountName
targetedItem := item
targetedItem . Params [ 0 ] = tDetails . nick
cStatus , _ := client . historyStatus ( config )
tStatus , _ := target . historyStatus ( config )
// add to ephemeral history
if cStatus == HistoryEphemeral {
targetedItem . CfCorrespondent = tDetails . nickCasefolded
client . history . Add ( targetedItem )
}
if tStatus == HistoryEphemeral && client != target {
item . CfCorrespondent = details . nickCasefolded
target . history . Add ( item )
}
if cStatus == HistoryPersistent || tStatus == HistoryPersistent {
targetedItem . CfCorrespondent = ""
client . server . historyDB . AddDirectMessage ( details . nickCasefolded , details . account , tDetails . nickCasefolded , tDetails . account , targetedItem )
}
return nil
}
2021-04-07 11:40:39 +02:00
func ( client * Client ) listTargets ( start , end history . Selector , limit int ) ( results [ ] history . TargetListing , err error ) {
var base , extras [ ] history . TargetListing
var chcfnames [ ] string
for _ , channel := range client . Channels ( ) {
_ , seq , err := client . server . GetHistorySequence ( channel , client , "" )
if seq == nil || err != nil {
continue
}
if seq . Ephemeral ( ) {
items , err := seq . Between ( history . Selector { } , history . Selector { } , 1 )
if err == nil && len ( items ) != 0 {
extras = append ( extras , history . TargetListing {
Time : items [ 0 ] . Message . Time ,
CfName : channel . NameCasefolded ( ) ,
} )
}
} else {
chcfnames = append ( chcfnames , channel . NameCasefolded ( ) )
}
}
persistentExtras , err := client . server . historyDB . ListChannels ( chcfnames )
if err == nil && len ( persistentExtras ) != 0 {
extras = append ( extras , persistentExtras ... )
}
2021-04-19 14:54:40 +02:00
_ , cSeq , err := client . server . GetHistorySequence ( nil , client , "" )
2021-04-07 11:40:39 +02:00
if err == nil && cSeq != nil {
correspondents , err := cSeq . ListCorrespondents ( start , end , limit )
if err == nil {
base = correspondents
}
}
results = history . MergeTargets ( base , extras , start . Time , end . Time , limit )
return results , nil
}
2021-04-19 14:54:40 +02:00
// latest PRIVMSG from all DM targets
func ( client * Client ) privmsgsBetween ( startTime , endTime time . Time , targetLimit , messageLimit int ) ( results [ ] history . Item , err error ) {
start := history . Selector { Time : startTime }
end := history . Selector { Time : endTime }
targets , err := client . listTargets ( start , end , targetLimit )
if err != nil {
return
}
for _ , target := range targets {
if strings . HasPrefix ( target . CfName , "#" ) {
continue
}
_ , seq , err := client . server . GetHistorySequence ( nil , client , target . CfName )
if err == nil && seq != nil {
items , err := seq . Between ( start , end , messageLimit )
if err == nil {
results = append ( results , items ... )
} else {
client . server . logger . Error ( "internal" , "error querying privmsg history" , client . Nick ( ) , target . CfName , err . Error ( ) )
}
}
}
return
}
2020-08-07 23:30:42 +02:00
func ( client * Client ) handleRegisterTimeout ( ) {
client . Quit ( fmt . Sprintf ( "Registration timeout: %v" , RegisterTimeout ) , nil )
client . destroy ( nil )
}
2020-06-12 21:51:48 +02:00
func ( client * Client ) copyLastSeen ( ) ( result map [ string ] time . Time ) {
client . stateMutex . RLock ( )
defer client . stateMutex . RUnlock ( )
result = make ( map [ string ] time . Time , len ( client . lastSeen ) )
for id , lastSeen := range client . lastSeen {
result [ id ] = lastSeen
}
return
}
2020-02-19 01:38:42 +01:00
// these are bit flags indicating what part of the client status is "dirty"
// and needs to be read from memory and written to the db
const (
IncludeChannels uint = 1 << iota
2020-02-27 08:13:31 +01:00
IncludeLastSeen
2020-05-19 20:38:56 +02:00
IncludeUserModes
2020-07-06 10:08:04 +02:00
IncludeRealname
2020-02-19 01:38:42 +01:00
)
func ( client * Client ) markDirty ( dirtyBits uint ) {
client . stateMutex . Lock ( )
alwaysOn := client . alwaysOn
client . dirtyBits = client . dirtyBits | dirtyBits
client . stateMutex . Unlock ( )
if alwaysOn {
client . wakeWriter ( )
}
}
func ( client * Client ) wakeWriter ( ) {
if client . writerSemaphore . TryAcquire ( ) {
go client . writeLoop ( )
}
}
func ( client * Client ) writeLoop ( ) {
for {
2020-06-29 06:30:27 +02:00
client . performWrite ( 0 )
2020-02-19 01:38:42 +01:00
client . writerSemaphore . Release ( )
client . stateMutex . RLock ( )
isDirty := client . dirtyBits != 0
client . stateMutex . RUnlock ( )
if ! isDirty || ! client . writerSemaphore . TryAcquire ( ) {
return
}
}
}
2020-06-29 06:30:27 +02:00
func ( client * Client ) performWrite ( additionalDirtyBits uint ) {
2020-02-19 01:38:42 +01:00
client . stateMutex . Lock ( )
2020-06-29 06:30:27 +02:00
dirtyBits := client . dirtyBits | additionalDirtyBits
2020-02-19 01:38:42 +01:00
client . dirtyBits = 0
account := client . account
client . stateMutex . Unlock ( )
if account == "" {
client . server . logger . Error ( "internal" , "attempting to persist logged-out client" , client . Nick ( ) )
return
}
2020-02-20 08:33:49 +01:00
if ( dirtyBits & IncludeChannels ) != 0 {
channels := client . Channels ( )
2021-01-21 03:13:18 +01:00
channelToModes := make ( map [ string ] alwaysOnChannelStatus , len ( channels ) )
2020-12-02 09:56:00 +01:00
for _ , channel := range channels {
2021-01-21 03:13:18 +01:00
chname , status := channel . alwaysOnStatus ( client )
channelToModes [ chname ] = status
2020-02-20 08:33:49 +01:00
}
2020-12-02 09:56:00 +01:00
client . server . accounts . saveChannels ( account , channelToModes )
2020-02-20 08:33:49 +01:00
}
2020-02-27 08:13:31 +01:00
if ( dirtyBits & IncludeLastSeen ) != 0 {
2020-06-12 21:51:48 +02:00
client . server . accounts . saveLastSeen ( account , client . copyLastSeen ( ) )
2020-02-19 01:38:42 +01:00
}
2020-05-19 20:38:56 +02:00
if ( dirtyBits & IncludeUserModes ) != 0 {
uModes := make ( modes . Modes , 0 , len ( modes . SupportedUserModes ) )
for _ , m := range modes . SupportedUserModes {
switch m {
case modes . Operator , modes . ServerNotice :
// these can't be persisted because they depend on the operator block
default :
if client . HasMode ( m ) {
uModes = append ( uModes , m )
}
}
}
client . server . accounts . saveModes ( account , uModes )
}
2020-07-06 10:08:04 +02:00
if ( dirtyBits & IncludeRealname ) != 0 {
client . server . accounts . saveRealname ( account , client . realname )
}
2020-02-19 01:38:42 +01:00
}
2020-06-29 06:30:27 +02:00
// Blocking store; see Channel.Store and Socket.BlockingWrite
func ( client * Client ) Store ( dirtyBits uint ) ( err error ) {
defer func ( ) {
client . stateMutex . Lock ( )
isDirty := client . dirtyBits != 0
client . stateMutex . Unlock ( )
if isDirty {
client . wakeWriter ( )
}
} ( )
client . writerSemaphore . Acquire ( )
defer client . writerSemaphore . Release ( )
client . performWrite ( dirtyBits )
return nil
}