1// Copyright (c) 2012-2014 Jeremy Latt
2// Copyright (c) 2014-2015 Edmund Huber
3// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
4// released under the MIT license
5
6package irc
7
8import (
9	"crypto/x509"
10	"fmt"
11	"net"
12	"runtime/debug"
13	"strconv"
14	"strings"
15	"sync"
16	"sync/atomic"
17	"time"
18
19	ident "github.com/ergochat/go-ident"
20	"github.com/ergochat/irc-go/ircfmt"
21	"github.com/ergochat/irc-go/ircmsg"
22	"github.com/ergochat/irc-go/ircreader"
23	"github.com/xdg-go/scram"
24
25	"github.com/ergochat/ergo/irc/caps"
26	"github.com/ergochat/ergo/irc/connection_limits"
27	"github.com/ergochat/ergo/irc/flatip"
28	"github.com/ergochat/ergo/irc/history"
29	"github.com/ergochat/ergo/irc/modes"
30	"github.com/ergochat/ergo/irc/sno"
31	"github.com/ergochat/ergo/irc/utils"
32)
33
34const (
35	// maximum IRC line length, not including tags
36	DefaultMaxLineLen = 512
37
38	// IdentTimeout is how long before our ident (username) check times out.
39	IdentTimeout         = time.Second + 500*time.Millisecond
40	IRCv3TimestampFormat = utils.IRCv3TimestampFormat
41	// limit the number of device IDs a client can use, as a DoS mitigation
42	maxDeviceIDsPerClient = 64
43	// controls how often often we write an autoreplay-missed client's
44	// deviceid->lastseentime mapping to the database
45	lastSeenWriteInterval = time.Hour
46)
47
48const (
49	// RegisterTimeout is how long clients have to register before we disconnect them
50	RegisterTimeout = time.Minute
51	// DefaultIdleTimeout is how long without traffic before we send the client a PING
52	DefaultIdleTimeout = time.Minute + 30*time.Second
53	// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
54	// (single-onion circuits will close unless the client sends data once every 60 seconds):
55	// https://bugs.torproject.org/29665
56	TorIdleTimeout = time.Second * 30
57	// This is how long a client gets without sending any message, including the PONG to our
58	// PING, before we disconnect them:
59	DefaultTotalTimeout = 2*time.Minute + 30*time.Second
60
61	// round off the ping interval by this much, see below:
62	PingCoalesceThreshold = time.Second
63)
64
65var (
66	MaxLineLen = DefaultMaxLineLen
67)
68
69// Client is an IRC client.
70type Client struct {
71	account            string
72	accountName        string // display name of the account: uncasefolded, '*' if not logged in
73	accountRegDate     time.Time
74	accountSettings    AccountSettings
75	awayMessage        string
76	channels           ChannelSet
77	ctime              time.Time
78	destroyed          bool
79	modes              modes.ModeSet
80	hostname           string
81	invitedTo          map[string]channelInvite
82	isSTSOnly          bool
83	languages          []string
84	lastActive         time.Time            // last time they sent a command that wasn't PONG or similar
85	lastSeen           map[string]time.Time // maps device ID (including "") to time of last received command
86	lastSeenLastWrite  time.Time            // last time `lastSeen` was written to the datastore
87	loginThrottle      connection_limits.GenericThrottle
88	nextSessionID      int64 // Incremented when a new session is established
89	nick               string
90	nickCasefolded     string
91	nickMaskCasefolded string
92	nickMaskString     string // cache for nickmask string since it's used with lots of replies
93	oper               *Oper
94	preregNick         string
95	proxiedIP          net.IP // actual remote IP if using the PROXY protocol
96	rawHostname        string
97	cloakedHostname    string
98	realname           string
99	realIP             net.IP
100	requireSASLMessage string
101	requireSASL        bool
102	registered         bool
103	registerCmdSent    bool // already sent the draft/register command, can't send it again
104	registrationTimer  *time.Timer
105	server             *Server
106	skeleton           string
107	sessions           []*Session
108	stateMutex         sync.RWMutex // tier 1
109	alwaysOn           bool
110	username           string
111	vhost              string
112	history            history.Buffer
113	dirtyBits          uint
114	writerSemaphore    utils.Semaphore // tier 1.5
115}
116
117type saslStatus struct {
118	mechanism string
119	value     string
120	scramConv *scram.ServerConversation
121}
122
123func (s *saslStatus) Clear() {
124	*s = saslStatus{}
125}
126
127// what stage the client is at w.r.t. the PASS command:
128type serverPassStatus uint
129
130const (
131	serverPassUnsent serverPassStatus = iota
132	serverPassSuccessful
133	serverPassFailed
134)
135
136// Session is an individual client connection to the server (TCP connection
137// and associated per-connection data, such as capabilities). There is a
138// many-one relationship between sessions and clients.
139type Session struct {
140	client *Client
141
142	deviceID string
143
144	ctime      time.Time
145	lastActive time.Time // last non-CTCP PRIVMSG sent; updates publicly visible idle time
146	lastTouch  time.Time // last line sent; updates timer for idle timeouts
147	idleTimer  *time.Timer
148	pingSent   bool // we sent PING to a putatively idle connection and we're waiting for PONG
149
150	sessionID   int64
151	socket      *Socket
152	realIP      net.IP
153	proxiedIP   net.IP
154	rawHostname string
155	isTor       bool
156	hideSTS     bool
157
158	fakelag              Fakelag
159	deferredFakelagCount int
160
161	certfp     string
162	peerCerts  []*x509.Certificate
163	sasl       saslStatus
164	passStatus serverPassStatus
165
166	batchCounter uint32
167
168	quitMessage string
169
170	awayMessage string
171	awayAt      time.Time
172
173	capabilities caps.Set
174	capState     caps.State
175	capVersion   caps.Version
176
177	registrationMessages int
178
179	zncPlaybackTimes      *zncPlaybackTimes
180	autoreplayMissedSince time.Time
181
182	batch MultilineBatch
183}
184
185// MultilineBatch tracks the state of a client-to-server multiline batch.
186type MultilineBatch struct {
187	label         string // this is the first param to BATCH (the "reference tag")
188	command       string
189	target        string
190	responseLabel string // this is the value of the labeled-response tag sent with BATCH
191	message       utils.SplitMessage
192	lenBytes      int
193	tags          map[string]string
194}
195
196// Starts a multiline batch, failing if there's one already open
197func (s *Session) StartMultilineBatch(label, target, responseLabel string, tags map[string]string) (err error) {
198	if s.batch.label != "" {
199		return errInvalidMultilineBatch
200	}
201
202	s.batch.label, s.batch.target, s.batch.responseLabel, s.batch.tags = label, target, responseLabel, tags
203	s.fakelag.Suspend()
204	return
205}
206
207// Closes a multiline batch unconditionally; returns the batch and whether
208// it was validly terminated (pass "" as the label if you don't care about the batch)
209func (s *Session) EndMultilineBatch(label string) (batch MultilineBatch, err error) {
210	batch = s.batch
211	s.batch = MultilineBatch{}
212	s.fakelag.Unsuspend()
213
214	// heuristics to estimate how much data they used while fakelag was suspended
215	fakelagBill := (batch.lenBytes / MaxLineLen) + 1
216	fakelagBillLines := (batch.message.LenLines() * 60) / MaxLineLen
217	if fakelagBill < fakelagBillLines {
218		fakelagBill = fakelagBillLines
219	}
220	s.deferredFakelagCount = fakelagBill
221
222	if batch.label == "" || batch.label != label || !batch.message.ValidMultiline() {
223		err = errInvalidMultilineBatch
224		return
225	}
226
227	batch.message.SetTime()
228
229	return
230}
231
232// sets the session quit message, if there isn't one already
233func (sd *Session) setQuitMessage(message string) (set bool) {
234	if message == "" {
235		message = "Connection closed"
236	}
237	if sd.quitMessage == "" {
238		sd.quitMessage = message
239		return true
240	} else {
241		return false
242	}
243}
244
245func (s *Session) IP() net.IP {
246	if s.proxiedIP != nil {
247		return s.proxiedIP
248	}
249	return s.realIP
250}
251
252// returns whether the client supports a smart history replay cap,
253// and therefore autoreplay-on-join and similar should be suppressed
254func (session *Session) HasHistoryCaps() bool {
255	return session.capabilities.Has(caps.Chathistory) || session.capabilities.Has(caps.ZNCPlayback)
256}
257
258// generates a batch ID. the uniqueness requirements for this are fairly weak:
259// any two batch IDs that are active concurrently (either through interleaving
260// or nesting) on an individual session connection need to be unique.
261// this allows ~4 billion such batches which should be fine.
262func (session *Session) generateBatchID() string {
263	id := atomic.AddUint32(&session.batchCounter, 1)
264	return strconv.FormatInt(int64(id), 32)
265}
266
267// WhoWas is the subset of client details needed to answer a WHOWAS query
268type WhoWas struct {
269	nick           string
270	nickCasefolded string
271	username       string
272	hostname       string
273	realname       string
274	ip             net.IP
275	// technically not required for WHOWAS:
276	account     string
277	accountName string
278}
279
280// ClientDetails is a standard set of details about a client
281type ClientDetails struct {
282	WhoWas
283
284	nickMask           string
285	nickMaskCasefolded string
286}
287
288// RunClient sets up a new client and runs its goroutine.
289func (server *Server) RunClient(conn IRCConn) {
290	config := server.Config()
291	wConn := conn.UnderlyingConn()
292	var isBanned, requireSASL bool
293	var banMsg string
294	realIP := utils.AddrToIP(wConn.RemoteAddr())
295	var proxiedIP net.IP
296	if wConn.Config.Tor {
297		// cover up details of the tor proxying infrastructure (not a user privacy concern,
298		// but a hardening measure):
299		proxiedIP = utils.IPv4LoopbackAddress
300		isBanned, banMsg = server.checkTorLimits()
301	} else {
302		ipToCheck := realIP
303		if wConn.ProxiedIP != nil {
304			proxiedIP = wConn.ProxiedIP
305			ipToCheck = proxiedIP
306		}
307		// XXX only run the check script now if the IP cannot be replaced by PROXY or WEBIRC,
308		// otherwise we'll do it in ApplyProxiedIP.
309		checkScripts := proxiedIP != nil || !utils.IPInNets(realIP, config.Server.proxyAllowedFromNets)
310		isBanned, requireSASL, banMsg = server.checkBans(config, ipToCheck, checkScripts)
311	}
312
313	if isBanned {
314		// this might not show up properly on some clients,
315		// but our objective here is just to close the connection out before it has a load impact on us
316		conn.WriteLine([]byte(fmt.Sprintf(errorMsg, banMsg)))
317		conn.Close()
318		return
319	}
320
321	server.logger.Info("connect-ip", fmt.Sprintf("Client connecting: real IP %v, proxied IP %v", realIP, proxiedIP))
322
323	now := time.Now().UTC()
324	// give them 1k of grace over the limit:
325	socket := NewSocket(conn, config.Server.MaxSendQBytes)
326	client := &Client{
327		lastActive: now,
328		channels:   make(ChannelSet),
329		ctime:      now,
330		isSTSOnly:  wConn.Config.STSOnly,
331		languages:  server.Languages().Default(),
332		loginThrottle: connection_limits.GenericThrottle{
333			Duration: config.Accounts.LoginThrottling.Duration,
334			Limit:    config.Accounts.LoginThrottling.MaxAttempts,
335		},
336		server:          server,
337		accountName:     "*",
338		nick:            "*", // * is used until actual nick is given
339		nickCasefolded:  "*",
340		nickMaskString:  "*", // * is used until actual nick is given
341		realIP:          realIP,
342		proxiedIP:       proxiedIP,
343		requireSASL:     requireSASL,
344		nextSessionID:   1,
345		writerSemaphore: utils.NewSemaphore(1),
346	}
347	if requireSASL {
348		client.requireSASLMessage = banMsg
349	}
350	client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
351	session := &Session{
352		client:     client,
353		socket:     socket,
354		capVersion: caps.Cap301,
355		capState:   caps.NoneState,
356		ctime:      now,
357		lastActive: now,
358		realIP:     realIP,
359		proxiedIP:  proxiedIP,
360		isTor:      wConn.Config.Tor,
361		hideSTS:    wConn.Config.Tor || wConn.Config.HideSTS,
362	}
363	client.sessions = []*Session{session}
364
365	session.resetFakelag()
366
367	if wConn.Secure {
368		client.SetMode(modes.TLS, true)
369	}
370
371	if wConn.Config.TLSConfig != nil {
372		// error is not useful to us here anyways so we can ignore it
373		session.certfp, session.peerCerts, _ = utils.GetCertFP(wConn.Conn, RegisterTimeout)
374	}
375
376	if session.isTor {
377		session.rawHostname = config.Server.TorListeners.Vhost
378		client.rawHostname = session.rawHostname
379	} else {
380		if config.Server.CheckIdent {
381			client.doIdentLookup(wConn.Conn)
382		}
383	}
384
385	client.registrationTimer = time.AfterFunc(RegisterTimeout, client.handleRegisterTimeout)
386	server.stats.Add()
387	client.run(session)
388}
389
390func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus map[string]alwaysOnChannelStatus, lastSeen map[string]time.Time, uModes modes.Modes, realname string) {
391	now := time.Now().UTC()
392	config := server.Config()
393	if lastSeen == nil && account.Settings.AutoreplayMissed {
394		lastSeen = map[string]time.Time{"": now}
395	}
396
397	rawHostname, cloakedHostname := server.name, ""
398	if config.Server.Cloaks.EnabledForAlwaysOn {
399		cloakedHostname = config.Server.Cloaks.ComputeAccountCloak(account.Name)
400	}
401
402	username := "~u"
403	if config.Server.CoerceIdent != "" {
404		username = config.Server.CoerceIdent
405	}
406
407	client := &Client{
408		lastSeen:   lastSeen,
409		lastActive: now,
410		channels:   make(ChannelSet),
411		ctime:      now,
412		languages:  server.Languages().Default(),
413		server:     server,
414
415		username:        username,
416		cloakedHostname: cloakedHostname,
417		rawHostname:     rawHostname,
418		realIP:          utils.IPv4LoopbackAddress,
419
420		alwaysOn: true,
421		realname: realname,
422
423		nextSessionID: 1,
424
425		writerSemaphore: utils.NewSemaphore(1),
426	}
427
428	if client.checkAlwaysOnExpirationNoMutex(config, true) {
429		server.logger.Debug("accounts", "always-on client not created due to expiration", account.Name)
430		return
431	}
432
433	client.SetMode(modes.TLS, true)
434	for _, m := range uModes {
435		client.SetMode(m, true)
436	}
437	client.history.Initialize(0, 0)
438
439	server.accounts.Login(client, account)
440
441	client.resizeHistory(config)
442
443	_, err, _ := server.clients.SetNick(client, nil, account.Name, false)
444	if err != nil {
445		server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
446		return
447	} else {
448		server.logger.Debug("accounts", "established always-on client", account.Name)
449	}
450
451	// XXX set this last to avoid confusing SetNick:
452	client.registered = true
453
454	for chname, status := range channelToStatus {
455		// XXX we're using isSajoin=true, to make these joins succeed even without channel key
456		// this is *probably* ok as long as the persisted memberships are accurate
457		server.channels.Join(client, chname, "", true, nil)
458		if channel := server.channels.Get(chname); channel != nil {
459			channel.setMemberStatus(client, status)
460		} else {
461			server.logger.Error("internal", "could not create channel", chname)
462		}
463	}
464
465	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
466		client.setAutoAwayNoMutex(config)
467	}
468}
469
470func (client *Client) resizeHistory(config *Config) {
471	status, _ := client.historyStatus(config)
472	if status == HistoryEphemeral {
473		client.history.Resize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
474	} else {
475		client.history.Resize(0, 0)
476	}
477}
478
479// resolve an IP to an IRC-ready hostname, using reverse DNS, forward-confirming if necessary,
480// and sending appropriate notices to the client
481func (client *Client) lookupHostname(session *Session, overwrite bool) {
482	if session.isTor {
483		return
484	} // else: even if cloaking is enabled, look up the real hostname to show to operators
485
486	config := client.server.Config()
487	ip := session.realIP
488	if session.proxiedIP != nil {
489		ip = session.proxiedIP
490	}
491
492	var hostname string
493	lookupSuccessful := false
494	if config.Server.lookupHostnames {
495		session.Notice("*** Looking up your hostname...")
496		hostname, lookupSuccessful = utils.LookupHostname(ip, config.Server.ForwardConfirmHostnames)
497		if lookupSuccessful {
498			session.Notice("*** Found your hostname")
499		} else {
500			session.Notice("*** Couldn't look up your hostname")
501		}
502	} else {
503		hostname = utils.IPStringToHostname(ip.String())
504	}
505
506	session.rawHostname = hostname
507	cloakedHostname := config.Server.Cloaks.ComputeCloak(ip)
508	client.stateMutex.Lock()
509	defer client.stateMutex.Unlock()
510	// update the hostname if this is a new connection, but not if it's a reattach
511	if overwrite || client.rawHostname == "" {
512		client.rawHostname = hostname
513		client.cloakedHostname = cloakedHostname
514		client.updateNickMaskNoMutex()
515	}
516}
517
518func (client *Client) doIdentLookup(conn net.Conn) {
519	localTCPAddr, ok := conn.LocalAddr().(*net.TCPAddr)
520	if !ok {
521		return
522	}
523	serverPort := localTCPAddr.Port
524	remoteTCPAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
525	if !ok {
526		return
527	}
528	clientPort := remoteTCPAddr.Port
529
530	client.Notice(client.t("*** Looking up your username"))
531	resp, err := ident.Query(remoteTCPAddr.IP.String(), serverPort, clientPort, IdentTimeout)
532	if err == nil {
533		err := client.SetNames(resp.Identifier, "", true)
534		if err == nil {
535			client.Notice(client.t("*** Found your username"))
536			// we don't need to updateNickMask here since nickMask is not used for anything yet
537		} else {
538			client.Notice(client.t("*** Got a malformed username, ignoring"))
539		}
540	} else {
541		client.Notice(client.t("*** Could not find your username"))
542	}
543}
544
545type AuthOutcome uint
546
547const (
548	authSuccess AuthOutcome = iota
549	authFailPass
550	authFailTorSaslRequired
551	authFailSaslRequired
552)
553
554func (client *Client) isAuthorized(server *Server, config *Config, session *Session, forceRequireSASL bool) AuthOutcome {
555	saslSent := client.account != ""
556	// PASS requirement
557	if (config.Server.passwordBytes != nil) && session.passStatus != serverPassSuccessful && !(config.Accounts.SkipServerPassword && saslSent) {
558		return authFailPass
559	}
560	// Tor connections may be required to authenticate with SASL
561	if session.isTor && !saslSent && (config.Server.TorListeners.RequireSasl || server.Defcon() <= 4) {
562		return authFailTorSaslRequired
563	}
564	// finally, enforce require-sasl
565	if !saslSent && (forceRequireSASL || config.Accounts.RequireSasl.Enabled || server.Defcon() <= 2) &&
566		!utils.IPInNets(session.IP(), config.Accounts.RequireSasl.exemptedNets) {
567		return authFailSaslRequired
568	}
569	return authSuccess
570}
571
572func (session *Session) resetFakelag() {
573	var flc FakelagConfig = session.client.server.Config().Fakelag
574	flc.Enabled = flc.Enabled && !session.client.HasRoleCapabs("nofakelag")
575	session.fakelag.Initialize(flc)
576}
577
578// IP returns the IP address of this client.
579func (client *Client) IP() net.IP {
580	client.stateMutex.RLock()
581	defer client.stateMutex.RUnlock()
582
583	return client.getIPNoMutex()
584}
585
586func (client *Client) getIPNoMutex() net.IP {
587	if client.proxiedIP != nil {
588		return client.proxiedIP
589	}
590	return client.realIP
591}
592
593// IPString returns the IP address of this client as a string.
594func (client *Client) IPString() string {
595	return utils.IPStringToHostname(client.IP().String())
596}
597
598// t returns the translated version of the given string, based on the languages configured by the client.
599func (client *Client) t(originalString string) string {
600	languageManager := client.server.Config().languageManager
601	if !languageManager.Enabled() {
602		return originalString
603	}
604	return languageManager.Translate(client.Languages(), originalString)
605}
606
607// main client goroutine: read lines and execute the corresponding commands
608// `proxyLine` is the PROXY-before-TLS line, if there was one
609func (client *Client) run(session *Session) {
610
611	defer func() {
612		if r := recover(); r != nil {
613			client.server.logger.Error("internal",
614				fmt.Sprintf("Client caused panic: %v\n%s", r, debug.Stack()))
615			if client.server.Config().Debug.recoverFromErrors {
616				client.server.logger.Error("internal", "Disconnecting client and attempting to recover")
617			} else {
618				panic(r)
619			}
620		}
621		// ensure client connection gets closed
622		client.destroy(session)
623	}()
624
625	isReattach := client.Registered()
626	if isReattach {
627		client.Touch(session)
628		client.playReattachMessages(session)
629	}
630
631	firstLine := !isReattach
632
633	for {
634		var invalidUtf8 bool
635		line, err := session.socket.Read()
636		if err == errInvalidUtf8 {
637			invalidUtf8 = true // handle as normal, including labeling
638		} else if err != nil {
639			client.server.logger.Debug("connect-ip", "read error from client", err.Error())
640			var quitMessage string
641			switch err {
642			case ircreader.ErrReadQ:
643				quitMessage = err.Error()
644			default:
645				quitMessage = "connection closed"
646			}
647			client.Quit(quitMessage, session)
648			break
649		}
650
651		if client.server.logger.IsLoggingRawIO() {
652			client.server.logger.Debug("userinput", client.nick, "<- ", line)
653		}
654
655		// special-cased handling of PROXY protocol, see `handleProxyCommand` for details:
656		if firstLine {
657			firstLine = false
658			if strings.HasPrefix(line, "PROXY") {
659				err = handleProxyCommand(client.server, client, session, line)
660				if err != nil {
661					break
662				} else {
663					continue
664				}
665			}
666		}
667
668		if client.registered {
669			touches := session.deferredFakelagCount + 1
670			session.deferredFakelagCount = 0
671			for i := 0; i < touches; i++ {
672				session.fakelag.Touch()
673			}
674		} else {
675			// DoS hardening, #505
676			session.registrationMessages++
677			if client.server.Config().Limits.RegistrationMessages < session.registrationMessages {
678				client.Send(nil, client.server.name, ERR_UNKNOWNERROR, "*", client.t("You have sent too many registration messages"))
679				break
680			}
681		}
682
683		msg, err := ircmsg.ParseLineStrict(line, true, MaxLineLen)
684		if err == ircmsg.ErrorLineIsEmpty {
685			continue
686		} else if err == ircmsg.ErrorTagsTooLong {
687			session.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.Nick(), client.t("Input line contained excess tag data"))
688			continue
689		} else if err == ircmsg.ErrorBodyTooLong {
690			if !client.server.Config().Server.Compatibility.allowTruncation {
691				session.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.Nick(), client.t("Input line too long"))
692				continue
693			} // else: proceed with the truncated line
694		} else if err != nil {
695			client.Quit(client.t("Received malformed line"), session)
696			break
697		}
698
699		cmd, exists := Commands[msg.Command]
700		if !exists {
701			cmd = unknownCommand
702		} else if invalidUtf8 {
703			cmd = invalidUtf8Command
704		}
705
706		isExiting := cmd.Run(client.server, client, session, msg)
707		if isExiting {
708			break
709		} else if session.client != client {
710			// bouncer reattach
711			go session.client.run(session)
712			break
713		}
714	}
715}
716
717func (client *Client) playReattachMessages(session *Session) {
718	client.server.playRegistrationBurst(session)
719	hasHistoryCaps := session.HasHistoryCaps()
720	for _, channel := range session.client.Channels() {
721		channel.playJoinForSession(session)
722		// clients should receive autoreplay-on-join lines, if applicable:
723		if hasHistoryCaps {
724			continue
725		}
726		// if they negotiated znc.in/playback or chathistory, they will receive nothing,
727		// because those caps disable autoreplay-on-join and they haven't sent the relevant
728		// *playback PRIVMSG or CHATHISTORY command yet
729		rb := NewResponseBuffer(session)
730		channel.autoReplayHistory(client, rb, "")
731		rb.Send(true)
732	}
733	if !session.autoreplayMissedSince.IsZero() && !hasHistoryCaps {
734		rb := NewResponseBuffer(session)
735		zncPlayPrivmsgsFromAll(client, rb, time.Now().UTC(), session.autoreplayMissedSince)
736		rb.Send(true)
737	}
738	session.autoreplayMissedSince = time.Time{}
739}
740
741//
742// idle, quit, timers and timeouts
743//
744
745// Touch indicates that we received a line from the client (so the connection is healthy
746// at this time, modulo network latency and fakelag).
747func (client *Client) Touch(session *Session) {
748	var markDirty bool
749	now := time.Now().UTC()
750	client.stateMutex.Lock()
751	if client.registered {
752		client.updateIdleTimer(session, now)
753		if client.alwaysOn {
754			client.setLastSeen(now, session.deviceID)
755			if now.Sub(client.lastSeenLastWrite) > lastSeenWriteInterval {
756				markDirty = true
757				client.lastSeenLastWrite = now
758			}
759		}
760	}
761	client.stateMutex.Unlock()
762	if markDirty {
763		client.markDirty(IncludeLastSeen)
764	}
765}
766
767func (client *Client) setLastSeen(now time.Time, deviceID string) {
768	if client.lastSeen == nil {
769		client.lastSeen = make(map[string]time.Time)
770	}
771	client.lastSeen[deviceID] = now
772	// evict the least-recently-used entry if necessary
773	if maxDeviceIDsPerClient < len(client.lastSeen) {
774		var minLastSeen time.Time
775		var minClientId string
776		for deviceID, lastSeen := range client.lastSeen {
777			if minLastSeen.IsZero() || lastSeen.Before(minLastSeen) {
778				minClientId, minLastSeen = deviceID, lastSeen
779			}
780		}
781		delete(client.lastSeen, minClientId)
782	}
783}
784
785func (client *Client) updateIdleTimer(session *Session, now time.Time) {
786	session.lastTouch = now
787	session.pingSent = false
788
789	if session.idleTimer == nil {
790		pingTimeout := DefaultIdleTimeout
791		if session.isTor {
792			pingTimeout = TorIdleTimeout
793		}
794		session.idleTimer = time.AfterFunc(pingTimeout, session.handleIdleTimeout)
795	}
796}
797
798func (session *Session) handleIdleTimeout() {
799	totalTimeout := DefaultTotalTimeout
800	pingTimeout := DefaultIdleTimeout
801	if session.isTor {
802		pingTimeout = TorIdleTimeout
803	}
804
805	session.client.stateMutex.Lock()
806	now := time.Now()
807	timeUntilDestroy := session.lastTouch.Add(totalTimeout).Sub(now)
808	timeUntilPing := session.lastTouch.Add(pingTimeout).Sub(now)
809	shouldDestroy := session.pingSent && timeUntilDestroy <= 0
810	// XXX this should really be time <= 0, but let's do some hacky timer coalescing:
811	// a typical idling client will do nothing other than respond immediately to our pings,
812	// so we'll PING at t=0, they'll respond at t=0.05, then we'll wake up at t=90 and find
813	// that we need to PING again at t=90.05. Rather than wake up again, just send it now:
814	shouldSendPing := !session.pingSent && timeUntilPing <= PingCoalesceThreshold
815	if !shouldDestroy {
816		if shouldSendPing {
817			session.pingSent = true
818		}
819		// check in again at the minimum of these 3 possible intervals:
820		// 1. the ping timeout (assuming we PING and they reply immediately with PONG)
821		// 2. the next time we would send PING (if they don't send any more lines)
822		// 3. the next time we would destroy (if they don't send any more lines)
823		nextTimeout := pingTimeout
824		if PingCoalesceThreshold < timeUntilPing && timeUntilPing < nextTimeout {
825			nextTimeout = timeUntilPing
826		}
827		if 0 < timeUntilDestroy && timeUntilDestroy < nextTimeout {
828			nextTimeout = timeUntilDestroy
829		}
830		session.idleTimer.Stop()
831		session.idleTimer.Reset(nextTimeout)
832	}
833	session.client.stateMutex.Unlock()
834
835	if shouldDestroy {
836		session.client.Quit(fmt.Sprintf("Ping timeout: %v", totalTimeout), session)
837		session.client.destroy(session)
838	} else if shouldSendPing {
839		session.Ping()
840	}
841}
842
843func (session *Session) stopIdleTimer() {
844	session.client.stateMutex.Lock()
845	defer session.client.stateMutex.Unlock()
846	if session.idleTimer != nil {
847		session.idleTimer.Stop()
848	}
849}
850
851// Ping sends the client a PING message.
852func (session *Session) Ping() {
853	session.Send(nil, "", "PING", session.client.Nick())
854}
855
856func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, target string, chathistoryCommand bool) {
857	var batchID string
858	details := client.Details()
859	nick := details.nick
860	if target == "" {
861		target = nick
862	}
863	batchID = rb.StartNestedHistoryBatch(target)
864
865	isSelfMessage := func(item *history.Item) bool {
866		// XXX: Params[0] is the message target. if the source of this message is an in-memory
867		// buffer, then it's "" for an incoming message and the recipient's nick for an outgoing
868		// message. if the source of the message is mysql, then mysql only sees one copy of the
869		// message, and it's the version with the recipient's nick filled in. so this is an
870		// incoming message if Params[0] (the recipient's nick) equals the client's nick:
871		return item.Params[0] != "" && item.Params[0] != nick
872	}
873
874	hasEventPlayback := rb.session.capabilities.Has(caps.EventPlayback)
875	hasTags := rb.session.capabilities.Has(caps.MessageTags)
876	for _, item := range items {
877		var command string
878		switch item.Type {
879		case history.Invite:
880			if isSelfMessage(&item) {
881				continue
882			}
883			if hasEventPlayback {
884				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "INVITE", nick, item.Message.Message)
885			} else {
886				rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(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))
887			}
888			continue
889		case history.Privmsg:
890			command = "PRIVMSG"
891		case history.Notice:
892			command = "NOTICE"
893		case history.Tagmsg:
894			if hasEventPlayback && hasTags {
895				command = "TAGMSG"
896			} else if chathistoryCommand {
897				// #1676: send something for TAGMSG; we can't discard it entirely
898				// because it'll break pagination
899				rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s sent you a TAGMSG"), NUHToNick(item.Nick)))
900			} else {
901				continue
902			}
903		default:
904			// see #1676, this shouldn't happen
905			continue
906		}
907		var tags map[string]string
908		if hasTags {
909			tags = item.Tags
910		}
911		if !isSelfMessage(&item) {
912			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, tags, command, nick, item.Message)
913		} else {
914			// this message was sent *from* the client to another nick; the target is item.Params[0]
915			// substitute client's current nickmask in case client changed nick
916			rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, item.IsBot, tags, command, item.Params[0], item.Message)
917		}
918	}
919
920	rb.EndNestedBatch(batchID)
921}
922
923// IdleTime returns how long this client's been idle.
924func (client *Client) IdleTime() time.Duration {
925	client.stateMutex.RLock()
926	defer client.stateMutex.RUnlock()
927	return time.Since(client.lastActive)
928}
929
930// SignonTime returns this client's signon time as a unix timestamp.
931func (client *Client) SignonTime() int64 {
932	return client.ctime.Unix()
933}
934
935// IdleSeconds returns the number of seconds this client's been idle.
936func (client *Client) IdleSeconds() uint64 {
937	return uint64(client.IdleTime().Seconds())
938}
939
940// SetNames sets the client's ident and realname.
941func (client *Client) SetNames(username, realname string, fromIdent bool) error {
942	config := client.server.Config()
943	limit := config.Limits.IdentLen
944	if !fromIdent {
945		limit -= 1 // leave room for the prepended ~
946	}
947	if limit < len(username) {
948		username = username[:limit]
949	}
950
951	if !isIdent(username) {
952		return errInvalidUsername
953	}
954
955	if config.Server.CoerceIdent != "" {
956		username = config.Server.CoerceIdent
957	} else if !fromIdent {
958		username = "~" + username
959	}
960
961	client.stateMutex.Lock()
962	defer client.stateMutex.Unlock()
963
964	if client.username == "" {
965		client.username = username
966	}
967
968	if client.realname == "" {
969		client.realname = realname
970	}
971
972	return nil
973}
974
975// HasRoleCapabs returns true if client has the given (role) capabilities.
976func (client *Client) HasRoleCapabs(capabs ...string) bool {
977	oper := client.Oper()
978	if oper == nil {
979		return false
980	}
981
982	for _, capab := range capabs {
983		if !oper.Class.Capabilities.Has(capab) {
984			return false
985		}
986	}
987
988	return true
989}
990
991// ModeString returns the mode string for this client.
992func (client *Client) ModeString() (str string) {
993	return "+" + client.modes.String()
994}
995
996// Friends refers to clients that share a channel with this client.
997func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]empty) {
998	result = make(map[*Session]empty)
999
1000	// look at the client's own sessions
1001	addFriendsToSet(result, client, capabs...)
1002
1003	for _, channel := range client.Channels() {
1004		for _, member := range channel.auditoriumFriends(client) {
1005			addFriendsToSet(result, member, capabs...)
1006		}
1007	}
1008
1009	return
1010}
1011
1012// Friends refers to clients that share a channel or extended-monitor this client.
1013func (client *Client) FriendsMonitors(capabs ...caps.Capability) (result map[*Session]empty) {
1014	result = client.Friends(capabs...)
1015	client.server.monitorManager.AddMonitors(result, client.nickCasefolded, capabs...)
1016	return
1017}
1018
1019// helper for Friends
1020func addFriendsToSet(set map[*Session]empty, client *Client, capabs ...caps.Capability) {
1021	client.stateMutex.RLock()
1022	defer client.stateMutex.RUnlock()
1023	for _, session := range client.sessions {
1024		if session.capabilities.HasAll(capabs...) {
1025			set[session] = empty{}
1026		}
1027	}
1028}
1029
1030func (client *Client) SetOper(oper *Oper) {
1031	client.stateMutex.Lock()
1032	defer client.stateMutex.Unlock()
1033	client.oper = oper
1034	// operators typically get a vhost, update the nickmask
1035	client.updateNickMaskNoMutex()
1036}
1037
1038// XXX: CHGHOST requires prefix nickmask to have original hostname,
1039// this is annoying to do correctly
1040func (client *Client) sendChghost(oldNickMask string, vhost string) {
1041	details := client.Details()
1042	isBot := client.HasMode(modes.Bot)
1043	for fClient := range client.FriendsMonitors(caps.ChgHost) {
1044		fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, details.accountName, isBot, nil, "CHGHOST", details.username, vhost)
1045	}
1046}
1047
1048// choose the correct vhost to display
1049func (client *Client) getVHostNoMutex() string {
1050	// hostserv vhost OR operclass vhost OR nothing (i.e., normal rdns hostmask)
1051	if client.vhost != "" {
1052		return client.vhost
1053	} else if client.oper != nil && !client.oper.Hidden {
1054		return client.oper.Vhost
1055	} else {
1056		return ""
1057	}
1058}
1059
1060// SetVHost updates the client's hostserv-based vhost
1061func (client *Client) SetVHost(vhost string) (updated bool) {
1062	client.stateMutex.Lock()
1063	defer client.stateMutex.Unlock()
1064	updated = (client.vhost != vhost)
1065	client.vhost = vhost
1066	if updated {
1067		client.updateNickMaskNoMutex()
1068	}
1069	return
1070}
1071
1072// SetNick gives the client a nickname and marks it as registered, if necessary
1073func (client *Client) SetNick(nick, nickCasefolded, skeleton string) (success bool) {
1074	client.stateMutex.Lock()
1075	defer client.stateMutex.Unlock()
1076	if client.destroyed {
1077		return false
1078	} else if !client.registered {
1079		// XXX test this before setting it to avoid annoying the race detector
1080		client.registered = true
1081		if client.registrationTimer != nil {
1082			client.registrationTimer.Stop()
1083			client.registrationTimer = nil
1084		}
1085	}
1086	client.nick = nick
1087	client.nickCasefolded = nickCasefolded
1088	client.skeleton = skeleton
1089	client.updateNickMaskNoMutex()
1090	return true
1091}
1092
1093// updateNickMaskNoMutex updates the casefolded nickname and nickmask, not acquiring any mutexes.
1094func (client *Client) updateNickMaskNoMutex() {
1095	if client.nick == "*" {
1096		return // pre-registration, don't bother generating the hostname
1097	}
1098
1099	client.hostname = client.getVHostNoMutex()
1100	if client.hostname == "" {
1101		client.hostname = client.cloakedHostname
1102		if client.hostname == "" {
1103			client.hostname = client.rawHostname
1104		}
1105	}
1106
1107	cfhostname := strings.ToLower(client.hostname)
1108	client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.hostname)
1109	client.nickMaskCasefolded = fmt.Sprintf("%s!%s@%s", client.nickCasefolded, strings.ToLower(client.username), cfhostname)
1110}
1111
1112// AllNickmasks returns all the possible nickmasks for the client.
1113func (client *Client) AllNickmasks() (masks []string) {
1114	client.stateMutex.RLock()
1115	nick := client.nickCasefolded
1116	username := client.username
1117	rawHostname := client.rawHostname
1118	cloakedHostname := client.cloakedHostname
1119	vhost := client.getVHostNoMutex()
1120	client.stateMutex.RUnlock()
1121	username = strings.ToLower(username)
1122
1123	if len(vhost) > 0 {
1124		cfvhost := strings.ToLower(vhost)
1125		masks = append(masks, fmt.Sprintf("%s!%s@%s", nick, username, cfvhost))
1126	}
1127
1128	var rawhostmask string
1129	cfrawhost := strings.ToLower(rawHostname)
1130	rawhostmask = fmt.Sprintf("%s!%s@%s", nick, username, cfrawhost)
1131	masks = append(masks, rawhostmask)
1132
1133	if cloakedHostname != "" {
1134		masks = append(masks, fmt.Sprintf("%s!%s@%s", nick, username, cloakedHostname))
1135	}
1136
1137	ipmask := fmt.Sprintf("%s!%s@%s", nick, username, client.IPString())
1138	if ipmask != rawhostmask {
1139		masks = append(masks, ipmask)
1140	}
1141
1142	return
1143}
1144
1145// LoggedIntoAccount returns true if this client is logged into an account.
1146func (client *Client) LoggedIntoAccount() bool {
1147	return client.Account() != ""
1148}
1149
1150// Quit sets the given quit message for the client.
1151// (You must ensure separately that destroy() is called, e.g., by returning `true` from
1152// the command handler or calling it yourself.)
1153func (client *Client) Quit(message string, session *Session) {
1154	setFinalData := func(sess *Session) {
1155		message := sess.quitMessage
1156		var finalData []byte
1157		// #364: don't send QUIT lines to unregistered clients
1158		if client.registered {
1159			quitMsg := ircmsg.MakeMessage(nil, client.nickMaskString, "QUIT", message)
1160			finalData, _ = quitMsg.LineBytesStrict(false, MaxLineLen)
1161		}
1162
1163		errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
1164		errorMsgBytes, _ := errorMsg.LineBytesStrict(false, MaxLineLen)
1165		finalData = append(finalData, errorMsgBytes...)
1166
1167		sess.socket.SetFinalData(finalData)
1168	}
1169
1170	client.stateMutex.Lock()
1171	defer client.stateMutex.Unlock()
1172
1173	var sessions []*Session
1174	if session != nil {
1175		sessions = []*Session{session}
1176	} else {
1177		sessions = client.sessions
1178	}
1179
1180	for _, session := range sessions {
1181		if session.setQuitMessage(message) {
1182			setFinalData(session)
1183		}
1184	}
1185}
1186
1187// destroy gets rid of a client, removes them from server lists etc.
1188// if `session` is nil, destroys the client unconditionally, removing all sessions;
1189// otherwise, destroys one specific session, only destroying the client if it
1190// has no more sessions.
1191func (client *Client) destroy(session *Session) {
1192	config := client.server.Config()
1193	var sessionsToDestroy []*Session
1194	var saveLastSeen bool
1195	var quitMessage string
1196
1197	client.stateMutex.Lock()
1198
1199	details := client.detailsNoMutex()
1200	wasReattach := session != nil && session.client != client
1201	sessionRemoved := false
1202	registered := client.registered
1203	// XXX a temporary (reattaching) client can be marked alwaysOn when it logs in,
1204	// but then the session attaches to another client and we need to clean it up here
1205	alwaysOn := registered && client.alwaysOn
1206	// if we hit always-on-expiration, confirm the expiration and then proceed as though
1207	// always-on is disabled:
1208	if alwaysOn && session == nil && client.checkAlwaysOnExpirationNoMutex(config, false) {
1209		quitMessage = "Timed out due to inactivity"
1210		alwaysOn = false
1211		client.alwaysOn = false
1212	}
1213
1214	var remainingSessions int
1215	if session == nil {
1216		sessionsToDestroy = client.sessions
1217		client.sessions = nil
1218		remainingSessions = 0
1219	} else {
1220		sessionRemoved, remainingSessions = client.removeSession(session)
1221		if sessionRemoved {
1222			sessionsToDestroy = []*Session{session}
1223		}
1224	}
1225
1226	// save last seen if applicable:
1227	if alwaysOn {
1228		if client.accountSettings.AutoreplayMissed {
1229			saveLastSeen = true
1230		} else {
1231			for _, session := range sessionsToDestroy {
1232				if session.deviceID != "" {
1233					saveLastSeen = true
1234					break
1235				}
1236			}
1237		}
1238	}
1239
1240	// should we destroy the whole client this time?
1241	shouldDestroy := !client.destroyed && remainingSessions == 0 && !alwaysOn
1242	// decrement stats on a true destroy, or for the removal of the last connected session
1243	// of an always-on client
1244	shouldDecrement := shouldDestroy || (alwaysOn && len(sessionsToDestroy) != 0 && len(client.sessions) == 0)
1245	if shouldDestroy {
1246		// if it's our job to destroy it, don't let anyone else try
1247		client.destroyed = true
1248	}
1249	if saveLastSeen {
1250		client.dirtyBits |= IncludeLastSeen
1251	}
1252
1253	becameAutoAway := false
1254	var awayMessage string
1255	if alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
1256		wasAway := client.awayMessage != ""
1257		client.setAutoAwayNoMutex(config)
1258		awayMessage = client.awayMessage
1259		becameAutoAway = !wasAway && awayMessage != ""
1260	}
1261
1262	if client.registrationTimer != nil {
1263		// unconditionally stop; if the client is still unregistered it must be destroyed
1264		client.registrationTimer.Stop()
1265	}
1266
1267	client.stateMutex.Unlock()
1268
1269	// XXX there is no particular reason to persist this state here rather than
1270	// any other place: it would be correct to persist it after every `Touch`. However,
1271	// I'm not comfortable introducing that many database writes, and I don't want to
1272	// design a throttle.
1273	if saveLastSeen {
1274		client.wakeWriter()
1275	}
1276
1277	// destroy all applicable sessions:
1278	for _, session := range sessionsToDestroy {
1279		if session.client != client {
1280			// session has been attached to a new client; do not destroy it
1281			continue
1282		}
1283		session.stopIdleTimer()
1284		// send quit/error message to client if they haven't been sent already
1285		client.Quit("", session)
1286		quitMessage = session.quitMessage // doesn't need synch, we already detached
1287		session.socket.Close()
1288
1289		// clean up monitor state
1290		client.server.monitorManager.RemoveAll(session)
1291
1292		// remove from connection limits
1293		var source string
1294		if session.isTor {
1295			client.server.torLimiter.RemoveClient()
1296			source = "tor"
1297		} else {
1298			ip := session.realIP
1299			if session.proxiedIP != nil {
1300				ip = session.proxiedIP
1301			}
1302			client.server.connectionLimiter.RemoveClient(flatip.FromNetIP(ip))
1303			source = ip.String()
1304		}
1305		if !shouldDestroy {
1306			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))
1307		}
1308		client.server.logger.Info("connect-ip", fmt.Sprintf("disconnecting session of %s from %s", details.nick, source))
1309	}
1310
1311	// decrement stats if we have no more sessions, even if the client will not be destroyed
1312	if shouldDecrement {
1313		invisible := client.HasMode(modes.Invisible)
1314		operator := client.HasMode(modes.Operator)
1315		client.server.stats.Remove(registered, invisible, operator)
1316	}
1317
1318	if becameAutoAway {
1319		dispatchAwayNotify(client, true, awayMessage)
1320	}
1321
1322	if !shouldDestroy {
1323		return
1324	}
1325
1326	var quitItem history.Item
1327	var channels []*Channel
1328	// use a defer here to avoid writing to mysql while holding the destroy semaphore:
1329	defer func() {
1330		for _, channel := range channels {
1331			channel.AddHistoryItem(quitItem, details.account)
1332		}
1333	}()
1334
1335	// see #235: deduplicating the list of PART recipients uses (comparatively speaking)
1336	// a lot of RAM, so limit concurrency to avoid thrashing
1337	client.server.semaphores.ClientDestroy.Acquire()
1338	defer client.server.semaphores.ClientDestroy.Release()
1339
1340	if !wasReattach {
1341		client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", details.nick))
1342	}
1343
1344	if registered {
1345		client.server.whoWas.Append(client.WhoWas())
1346	}
1347
1348	// alert monitors
1349	if registered {
1350		client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false)
1351	}
1352
1353	// clean up channels
1354	// (note that if this is a reattach, client has no channels and therefore no friends)
1355	friends := make(ClientSet)
1356	channels = client.Channels()
1357	for _, channel := range channels {
1358		for _, member := range channel.auditoriumFriends(client) {
1359			friends.Add(member)
1360		}
1361		channel.Quit(client)
1362	}
1363	friends.Remove(client)
1364
1365	// clean up server
1366	client.server.clients.Remove(client)
1367
1368	// clean up self
1369	client.server.accounts.Logout(client)
1370
1371	if quitMessage == "" {
1372		quitMessage = "Exited"
1373	}
1374	splitQuitMessage := utils.MakeMessage(quitMessage)
1375	isBot := client.HasMode(modes.Bot)
1376	quitItem = history.Item{
1377		Type:        history.Quit,
1378		Nick:        details.nickMask,
1379		AccountName: details.accountName,
1380		Message:     splitQuitMessage,
1381		IsBot:       isBot,
1382	}
1383	var cache MessageCache
1384	cache.Initialize(client.server, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, isBot, nil, "QUIT", quitMessage)
1385	for friend := range friends {
1386		for _, session := range friend.Sessions() {
1387			cache.Send(session)
1388		}
1389	}
1390
1391	if registered {
1392		client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), details.nick))
1393	}
1394}
1395
1396// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
1397// Adds account-tag to the line as well.
1398func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) {
1399	if message.Is512() {
1400		session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, isBot, tags, command, target, message.Message)
1401	} else {
1402		if session.capabilities.Has(caps.Multiline) {
1403			for _, msg := range composeMultilineBatch(session.generateBatchID(), nickmask, accountName, isBot, tags, command, target, message) {
1404				session.SendRawMessage(msg, blocking)
1405			}
1406		} else {
1407			msgidSent := false // send msgid on the first nonblank line
1408			for _, messagePair := range message.Split {
1409				if len(messagePair.Message) == 0 {
1410					continue
1411				}
1412				var msgid string
1413				if !msgidSent {
1414					msgidSent = true
1415					msgid = message.Msgid
1416				}
1417				session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, isBot, tags, command, target, messagePair.Message)
1418			}
1419		}
1420	}
1421}
1422
1423func (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) {
1424	msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
1425	// attach account-tag
1426	if session.capabilities.Has(caps.AccountTag) && accountName != "*" {
1427		msg.SetTag("account", accountName)
1428	}
1429	// attach message-id
1430	if msgid != "" && session.capabilities.Has(caps.MessageTags) {
1431		msg.SetTag("msgid", msgid)
1432	}
1433	// attach server-time
1434	session.setTimeTag(&msg, serverTime)
1435	// attach bot tag
1436	if isBot && session.capabilities.Has(caps.MessageTags) {
1437		msg.SetTag(caps.BotTagName, "")
1438	}
1439
1440	return session.SendRawMessage(msg, blocking)
1441}
1442
1443func composeMultilineBatch(batchID, fromNickMask, fromAccount string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.Message) {
1444	batchStart := ircmsg.MakeMessage(tags, fromNickMask, "BATCH", "+"+batchID, caps.MultilineBatchType, target)
1445	batchStart.SetTag("time", message.Time.Format(IRCv3TimestampFormat))
1446	batchStart.SetTag("msgid", message.Msgid)
1447	if fromAccount != "*" {
1448		batchStart.SetTag("account", fromAccount)
1449	}
1450	if isBot {
1451		batchStart.SetTag(caps.BotTagName, "")
1452	}
1453	result = append(result, batchStart)
1454
1455	for _, msg := range message.Split {
1456		message := ircmsg.MakeMessage(nil, fromNickMask, command, target, msg.Message)
1457		message.SetTag("batch", batchID)
1458		if msg.Concat {
1459			message.SetTag(caps.MultilineConcatTag, "")
1460		}
1461		result = append(result, message)
1462	}
1463
1464	result = append(result, ircmsg.MakeMessage(nil, fromNickMask, "BATCH", "-"+batchID))
1465	return
1466}
1467
1468var (
1469	// these are all the output commands that MUST have their last param be a trailing.
1470	// this is needed because dumb clients like to treat trailing params separately from the
1471	// other params in messages.
1472	commandsThatMustUseTrailing = map[string]bool{
1473		"PRIVMSG": true,
1474		"NOTICE":  true,
1475
1476		RPL_WHOISCHANNELS: true,
1477		RPL_USERHOST:      true,
1478
1479		// mirc's handling of RPL_NAMREPLY is broken:
1480		// https://forums.mirc.com/ubbthreads.php/topics/266939/re-nick-list
1481		RPL_NAMREPLY: true,
1482	}
1483)
1484
1485// SendRawMessage sends a raw message to the client.
1486func (session *Session) SendRawMessage(message ircmsg.Message, blocking bool) error {
1487	// use dumb hack to force the last param to be a trailing param if required
1488	config := session.client.server.Config()
1489	if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[message.Command] {
1490		message.ForceTrailing()
1491	}
1492
1493	// assemble message
1494	line, err := message.LineBytesStrict(false, MaxLineLen)
1495	if !(err == nil || err == ircmsg.ErrorBodyTooLong) {
1496		errorParams := []string{"Error assembling message for sending", err.Error(), message.Command}
1497		errorParams = append(errorParams, message.Params...)
1498		session.client.server.logger.Error("internal", errorParams...)
1499
1500		message = ircmsg.MakeMessage(nil, session.client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
1501		line, _ := message.LineBytesStrict(false, 0)
1502
1503		if blocking {
1504			session.socket.BlockingWrite(line)
1505		} else {
1506			session.socket.Write(line)
1507		}
1508		return err
1509	}
1510
1511	return session.sendBytes(line, blocking)
1512}
1513
1514func (session *Session) sendBytes(line []byte, blocking bool) (err error) {
1515	if session.client.server.logger.IsLoggingRawIO() {
1516		logline := string(line[:len(line)-2]) // strip "\r\n"
1517		session.client.server.logger.Debug("useroutput", session.client.Nick(), " ->", logline)
1518	}
1519
1520	if blocking {
1521		err = session.socket.BlockingWrite(line)
1522	} else {
1523		err = session.socket.Write(line)
1524	}
1525	if err != nil {
1526		session.client.server.logger.Info("quit", "send error to client", fmt.Sprintf("%s [%d]", session.client.Nick(), session.sessionID), err.Error())
1527	}
1528	return err
1529}
1530
1531// Send sends an IRC line to the client.
1532func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
1533	for _, session := range client.Sessions() {
1534		err_ := session.Send(tags, prefix, command, params...)
1535		if err_ != nil {
1536			err = err_
1537		}
1538	}
1539	return
1540}
1541
1542func (session *Session) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
1543	msg := ircmsg.MakeMessage(tags, prefix, command, params...)
1544	session.setTimeTag(&msg, time.Time{})
1545	return session.SendRawMessage(msg, false)
1546}
1547
1548func (session *Session) setTimeTag(msg *ircmsg.Message, serverTime time.Time) {
1549	if session.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
1550		if serverTime.IsZero() {
1551			serverTime = time.Now()
1552		}
1553		msg.SetTag("time", serverTime.UTC().Format(IRCv3TimestampFormat))
1554	}
1555}
1556
1557// Notice sends the client a notice from the server.
1558func (client *Client) Notice(text string) {
1559	client.Send(nil, client.server.name, "NOTICE", client.Nick(), text)
1560}
1561
1562func (session *Session) Notice(text string) {
1563	session.Send(nil, session.client.server.name, "NOTICE", session.client.Nick(), text)
1564}
1565
1566// `simulated` is for the fake join of an always-on client
1567// (we just read the channel name from the database, there's no need to write it back)
1568func (client *Client) addChannel(channel *Channel, simulated bool) (err error) {
1569	config := client.server.Config()
1570
1571	client.stateMutex.Lock()
1572	alwaysOn := client.alwaysOn
1573	if client.destroyed {
1574		err = errClientDestroyed
1575	} else if client.oper == nil && len(client.channels) >= config.Channels.MaxChannelsPerClient {
1576		err = errTooManyChannels
1577	} else {
1578		client.channels[channel] = empty{} // success
1579	}
1580	client.stateMutex.Unlock()
1581
1582	if err == nil && alwaysOn && !simulated {
1583		client.markDirty(IncludeChannels)
1584	}
1585	return
1586}
1587
1588func (client *Client) removeChannel(channel *Channel) {
1589	client.stateMutex.Lock()
1590	delete(client.channels, channel)
1591	alwaysOn := client.alwaysOn
1592	client.stateMutex.Unlock()
1593
1594	if alwaysOn {
1595		client.markDirty(IncludeChannels)
1596	}
1597}
1598
1599type channelInvite struct {
1600	channelCreatedAt time.Time
1601	invitedAt        time.Time
1602}
1603
1604// Records that the client has been invited to join an invite-only channel
1605func (client *Client) Invite(casefoldedChannel string, channelCreatedAt time.Time) {
1606	now := time.Now().UTC()
1607	client.stateMutex.Lock()
1608	defer client.stateMutex.Unlock()
1609
1610	if client.invitedTo == nil {
1611		client.invitedTo = make(map[string]channelInvite)
1612	}
1613
1614	client.invitedTo[casefoldedChannel] = channelInvite{
1615		channelCreatedAt: channelCreatedAt,
1616		invitedAt:        now,
1617	}
1618
1619	return
1620}
1621
1622func (client *Client) Uninvite(casefoldedChannel string) {
1623	client.stateMutex.Lock()
1624	defer client.stateMutex.Unlock()
1625	delete(client.invitedTo, casefoldedChannel)
1626}
1627
1628// Checks that the client was invited to join a given channel
1629func (client *Client) CheckInvited(casefoldedChannel string, createdTime time.Time) (invited bool) {
1630	config := client.server.Config()
1631	expTime := time.Duration(config.Channels.InviteExpiration)
1632	now := time.Now().UTC()
1633
1634	client.stateMutex.Lock()
1635	defer client.stateMutex.Unlock()
1636
1637	curInvite, ok := client.invitedTo[casefoldedChannel]
1638	if ok {
1639		// joining an invited channel "uses up" your invite, so you can't rejoin on kick
1640		delete(client.invitedTo, casefoldedChannel)
1641	}
1642	invited = ok && (expTime == time.Duration(0) || now.Sub(curInvite.invitedAt) < expTime) &&
1643		createdTime.Equal(curInvite.channelCreatedAt)
1644	return
1645}
1646
1647// Implements auto-oper by certfp (scans for an auto-eligible operator block that matches
1648// the client's cert, then applies it).
1649func (client *Client) attemptAutoOper(session *Session) {
1650	if session.certfp == "" || client.HasMode(modes.Operator) {
1651		return
1652	}
1653	for _, oper := range client.server.Config().operators {
1654		if oper.Auto && oper.Pass == nil && oper.Certfp != "" && oper.Certfp == session.certfp {
1655			rb := NewResponseBuffer(session)
1656			applyOper(client, oper, rb)
1657			rb.Send(true)
1658			return
1659		}
1660	}
1661}
1662
1663func (client *Client) checkLoginThrottle() (throttled bool, remainingTime time.Duration) {
1664	client.stateMutex.Lock()
1665	defer client.stateMutex.Unlock()
1666	return client.loginThrottle.Touch()
1667}
1668
1669func (client *Client) historyStatus(config *Config) (status HistoryStatus, target string) {
1670	if !config.History.Enabled {
1671		return HistoryDisabled, ""
1672	}
1673
1674	client.stateMutex.RLock()
1675	target = client.account
1676	historyStatus := client.accountSettings.DMHistory
1677	client.stateMutex.RUnlock()
1678
1679	if target == "" {
1680		return HistoryEphemeral, ""
1681	}
1682	status = historyEnabled(config.History.Persistent.DirectMessages, historyStatus)
1683	if status != HistoryPersistent {
1684		target = ""
1685	}
1686	return
1687}
1688
1689func (client *Client) addHistoryItem(target *Client, item history.Item, details, tDetails *ClientDetails, config *Config) (err error) {
1690	if !itemIsStorable(&item, config) {
1691		return
1692	}
1693
1694	item.Nick = details.nickMask
1695	item.AccountName = details.accountName
1696	targetedItem := item
1697	targetedItem.Params[0] = tDetails.nick
1698
1699	cStatus, _ := client.historyStatus(config)
1700	tStatus, _ := target.historyStatus(config)
1701	// add to ephemeral history
1702	if cStatus == HistoryEphemeral {
1703		targetedItem.CfCorrespondent = tDetails.nickCasefolded
1704		client.history.Add(targetedItem)
1705	}
1706	if tStatus == HistoryEphemeral && client != target {
1707		item.CfCorrespondent = details.nickCasefolded
1708		target.history.Add(item)
1709	}
1710	if cStatus == HistoryPersistent || tStatus == HistoryPersistent {
1711		targetedItem.CfCorrespondent = ""
1712		client.server.historyDB.AddDirectMessage(details.nickCasefolded, details.account, tDetails.nickCasefolded, tDetails.account, targetedItem)
1713	}
1714	return nil
1715}
1716
1717func (client *Client) listTargets(start, end history.Selector, limit int) (results []history.TargetListing, err error) {
1718	var base, extras []history.TargetListing
1719	var chcfnames []string
1720	for _, channel := range client.Channels() {
1721		_, seq, err := client.server.GetHistorySequence(channel, client, "")
1722		if seq == nil || err != nil {
1723			continue
1724		}
1725		if seq.Ephemeral() {
1726			items, err := seq.Between(history.Selector{}, history.Selector{}, 1)
1727			if err == nil && len(items) != 0 {
1728				extras = append(extras, history.TargetListing{
1729					Time:   items[0].Message.Time,
1730					CfName: channel.NameCasefolded(),
1731				})
1732			}
1733		} else {
1734			chcfnames = append(chcfnames, channel.NameCasefolded())
1735		}
1736	}
1737	persistentExtras, err := client.server.historyDB.ListChannels(chcfnames)
1738	if err == nil && len(persistentExtras) != 0 {
1739		extras = append(extras, persistentExtras...)
1740	}
1741
1742	_, cSeq, err := client.server.GetHistorySequence(nil, client, "")
1743	if err == nil && cSeq != nil {
1744		correspondents, err := cSeq.ListCorrespondents(start, end, limit)
1745		if err == nil {
1746			base = correspondents
1747		}
1748	}
1749
1750	results = history.MergeTargets(base, extras, start.Time, end.Time, limit)
1751	return results, nil
1752}
1753
1754// latest PRIVMSG from all DM targets
1755func (client *Client) privmsgsBetween(startTime, endTime time.Time, targetLimit, messageLimit int) (results []history.Item, err error) {
1756	start := history.Selector{Time: startTime}
1757	end := history.Selector{Time: endTime}
1758	targets, err := client.listTargets(start, end, targetLimit)
1759	if err != nil {
1760		return
1761	}
1762	for _, target := range targets {
1763		if strings.HasPrefix(target.CfName, "#") {
1764			continue
1765		}
1766		_, seq, err := client.server.GetHistorySequence(nil, client, target.CfName)
1767		if err == nil && seq != nil {
1768			items, err := seq.Between(start, end, messageLimit)
1769			if err == nil {
1770				results = append(results, items...)
1771			} else {
1772				client.server.logger.Error("internal", "error querying privmsg history", client.Nick(), target.CfName, err.Error())
1773			}
1774		}
1775	}
1776	return
1777}
1778
1779func (client *Client) handleRegisterTimeout() {
1780	client.Quit(fmt.Sprintf("Registration timeout: %v", RegisterTimeout), nil)
1781	client.destroy(nil)
1782}
1783
1784func (client *Client) copyLastSeen() (result map[string]time.Time) {
1785	client.stateMutex.RLock()
1786	defer client.stateMutex.RUnlock()
1787	result = make(map[string]time.Time, len(client.lastSeen))
1788	for id, lastSeen := range client.lastSeen {
1789		result[id] = lastSeen
1790	}
1791	return
1792}
1793
1794// these are bit flags indicating what part of the client status is "dirty"
1795// and needs to be read from memory and written to the db
1796const (
1797	IncludeChannels uint = 1 << iota
1798	IncludeLastSeen
1799	IncludeUserModes
1800	IncludeRealname
1801)
1802
1803func (client *Client) markDirty(dirtyBits uint) {
1804	client.stateMutex.Lock()
1805	alwaysOn := client.alwaysOn
1806	client.dirtyBits = client.dirtyBits | dirtyBits
1807	client.stateMutex.Unlock()
1808
1809	if alwaysOn {
1810		client.wakeWriter()
1811	}
1812}
1813
1814func (client *Client) wakeWriter() {
1815	if client.writerSemaphore.TryAcquire() {
1816		go client.writeLoop()
1817	}
1818}
1819
1820func (client *Client) writeLoop() {
1821	for {
1822		client.performWrite(0)
1823		client.writerSemaphore.Release()
1824
1825		client.stateMutex.RLock()
1826		isDirty := client.dirtyBits != 0
1827		client.stateMutex.RUnlock()
1828
1829		if !isDirty || !client.writerSemaphore.TryAcquire() {
1830			return
1831		}
1832	}
1833}
1834
1835func (client *Client) performWrite(additionalDirtyBits uint) {
1836	client.stateMutex.Lock()
1837	dirtyBits := client.dirtyBits | additionalDirtyBits
1838	client.dirtyBits = 0
1839	account := client.account
1840	client.stateMutex.Unlock()
1841
1842	if account == "" {
1843		client.server.logger.Error("internal", "attempting to persist logged-out client", client.Nick())
1844		return
1845	}
1846
1847	if (dirtyBits & IncludeChannels) != 0 {
1848		channels := client.Channels()
1849		channelToModes := make(map[string]alwaysOnChannelStatus, len(channels))
1850		for _, channel := range channels {
1851			chname, status := channel.alwaysOnStatus(client)
1852			channelToModes[chname] = status
1853		}
1854		client.server.accounts.saveChannels(account, channelToModes)
1855	}
1856	if (dirtyBits & IncludeLastSeen) != 0 {
1857		client.server.accounts.saveLastSeen(account, client.copyLastSeen())
1858	}
1859	if (dirtyBits & IncludeUserModes) != 0 {
1860		uModes := make(modes.Modes, 0, len(modes.SupportedUserModes))
1861		for _, m := range modes.SupportedUserModes {
1862			switch m {
1863			case modes.Operator, modes.ServerNotice:
1864				// these can't be persisted because they depend on the operator block
1865			default:
1866				if client.HasMode(m) {
1867					uModes = append(uModes, m)
1868				}
1869			}
1870		}
1871		client.server.accounts.saveModes(account, uModes)
1872	}
1873	if (dirtyBits & IncludeRealname) != 0 {
1874		client.server.accounts.saveRealname(account, client.realname)
1875	}
1876}
1877
1878// Blocking store; see Channel.Store and Socket.BlockingWrite
1879func (client *Client) Store(dirtyBits uint) (err error) {
1880	defer func() {
1881		client.stateMutex.Lock()
1882		isDirty := client.dirtyBits != 0
1883		client.stateMutex.Unlock()
1884
1885		if isDirty {
1886			client.wakeWriter()
1887		}
1888	}()
1889
1890	client.writerSemaphore.Acquire()
1891	defer client.writerSemaphore.Release()
1892	client.performWrite(dirtyBits)
1893	return nil
1894}
1895