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