You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

client.go 39KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  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. package irc
  6. import (
  7. "fmt"
  8. "net"
  9. "runtime/debug"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "sync/atomic"
  14. "time"
  15. "github.com/goshuirc/irc-go/ircfmt"
  16. "github.com/goshuirc/irc-go/ircmsg"
  17. ident "github.com/oragono/go-ident"
  18. "github.com/oragono/oragono/irc/caps"
  19. "github.com/oragono/oragono/irc/connection_limits"
  20. "github.com/oragono/oragono/irc/history"
  21. "github.com/oragono/oragono/irc/modes"
  22. "github.com/oragono/oragono/irc/sno"
  23. "github.com/oragono/oragono/irc/utils"
  24. )
  25. const (
  26. // IdentTimeoutSeconds is how many seconds before our ident (username) check times out.
  27. IdentTimeoutSeconds = 1.5
  28. IRCv3TimestampFormat = "2006-01-02T15:04:05.000Z"
  29. )
  30. // ResumeDetails is a place to stash data at various stages of
  31. // the resume process: when handling the RESUME command itself,
  32. // when completing the registration, and when rejoining channels.
  33. type ResumeDetails struct {
  34. PresentedToken string
  35. Timestamp time.Time
  36. HistoryIncomplete bool
  37. }
  38. // Client is an IRC client.
  39. type Client struct {
  40. account string
  41. accountName string // display name of the account: uncasefolded, '*' if not logged in
  42. accountSettings AccountSettings
  43. atime time.Time
  44. away bool
  45. awayMessage string
  46. brbTimer BrbTimer
  47. certfp string
  48. channels ChannelSet
  49. ctime time.Time
  50. destroyed bool
  51. exitedSnomaskSent bool
  52. flags modes.ModeSet
  53. hostname string
  54. invitedTo map[string]bool
  55. isSTSOnly bool
  56. isTor bool
  57. languages []string
  58. loginThrottle connection_limits.GenericThrottle
  59. nick string
  60. nickCasefolded string
  61. nickMaskCasefolded string
  62. nickMaskString string // cache for nickmask string since it's used with lots of replies
  63. nickTimer NickTimer
  64. oper *Oper
  65. preregNick string
  66. proxiedIP net.IP // actual remote IP if using the PROXY protocol
  67. rawHostname string
  68. cloakedHostname string
  69. realname string
  70. realIP net.IP
  71. registered bool
  72. resumeID string
  73. saslInProgress bool
  74. saslMechanism string
  75. saslValue string
  76. sentPassCommand bool
  77. server *Server
  78. skeleton string
  79. sessions []*Session
  80. stateMutex sync.RWMutex // tier 1
  81. username string
  82. vhost string
  83. history history.Buffer
  84. }
  85. // Session is an individual client connection to the server (TCP connection
  86. // and associated per-connection data, such as capabilities). There is a
  87. // many-one relationship between sessions and clients.
  88. type Session struct {
  89. client *Client
  90. ctime time.Time
  91. atime time.Time
  92. socket *Socket
  93. realIP net.IP
  94. proxiedIP net.IP
  95. rawHostname string
  96. idletimer IdleTimer
  97. fakelag Fakelag
  98. destroyed uint32
  99. quitMessage string
  100. capabilities caps.Set
  101. maxlenRest uint32
  102. capState caps.State
  103. capVersion caps.Version
  104. registrationMessages int
  105. resumeID string
  106. resumeDetails *ResumeDetails
  107. zncPlaybackTimes *zncPlaybackTimes
  108. }
  109. // sets the session quit message, if there isn't one already
  110. func (sd *Session) SetQuitMessage(message string) (set bool) {
  111. if message == "" {
  112. message = "Connection closed"
  113. }
  114. if sd.quitMessage == "" {
  115. sd.quitMessage = message
  116. return true
  117. } else {
  118. return false
  119. }
  120. }
  121. // set the negotiated message length based on session capabilities
  122. func (session *Session) SetMaxlenRest() {
  123. maxlenRest := 512
  124. if session.capabilities.Has(caps.MaxLine) {
  125. maxlenRest = session.client.server.Config().Limits.LineLen.Rest
  126. }
  127. atomic.StoreUint32(&session.maxlenRest, uint32(maxlenRest))
  128. }
  129. // allow the negotiated message length limit to be read without locks; this is a convenience
  130. // so that Session.SendRawMessage doesn't have to acquire any Client locks
  131. func (session *Session) MaxlenRest() int {
  132. return int(atomic.LoadUint32(&session.maxlenRest))
  133. }
  134. // returns whether the session was actively destroyed (for example, by ping
  135. // timeout or NS GHOST).
  136. // avoids a race condition between asynchronous idle-timing-out of sessions,
  137. // and a condition that allows implicit BRB on connection errors (since
  138. // destroy()'s socket.Close() appears to socket.Read() as a connection error)
  139. func (session *Session) Destroyed() bool {
  140. return atomic.LoadUint32(&session.destroyed) == 1
  141. }
  142. // sets the timed-out flag
  143. func (session *Session) SetDestroyed() {
  144. atomic.StoreUint32(&session.destroyed, 1)
  145. }
  146. // returns whether the client supports a smart history replay cap,
  147. // and therefore autoreplay-on-join and similar should be suppressed
  148. func (session *Session) HasHistoryCaps() bool {
  149. // TODO the chathistory cap will go here as well
  150. return session.capabilities.Has(caps.ZNCPlayback)
  151. }
  152. // WhoWas is the subset of client details needed to answer a WHOWAS query
  153. type WhoWas struct {
  154. nick string
  155. nickCasefolded string
  156. username string
  157. hostname string
  158. realname string
  159. }
  160. // ClientDetails is a standard set of details about a client
  161. type ClientDetails struct {
  162. WhoWas
  163. nickMask string
  164. nickMaskCasefolded string
  165. account string
  166. accountName string
  167. }
  168. // RunClient sets up a new client and runs its goroutine.
  169. func (server *Server) RunClient(conn clientConn) {
  170. var isBanned bool
  171. var banMsg string
  172. var realIP net.IP
  173. if conn.Config.IsTor {
  174. realIP = utils.IPv4LoopbackAddress
  175. isBanned, banMsg = server.checkTorLimits()
  176. } else {
  177. realIP = utils.AddrToIP(conn.Conn.RemoteAddr())
  178. isBanned, banMsg = server.checkBans(realIP)
  179. }
  180. if isBanned {
  181. // this might not show up properly on some clients,
  182. // but our objective here is just to close the connection out before it has a load impact on us
  183. conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
  184. conn.Conn.Close()
  185. return
  186. }
  187. server.logger.Info("localconnect-ip", fmt.Sprintf("Client connecting from %v", realIP))
  188. now := time.Now().UTC()
  189. config := server.Config()
  190. fullLineLenLimit := ircmsg.MaxlenTagsFromClient + config.Limits.LineLen.Rest
  191. // give them 1k of grace over the limit:
  192. socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
  193. client := &Client{
  194. atime: now,
  195. channels: make(ChannelSet),
  196. ctime: now,
  197. isSTSOnly: conn.Config.IsSTSOnly,
  198. isTor: conn.Config.IsTor,
  199. languages: server.Languages().Default(),
  200. loginThrottle: connection_limits.GenericThrottle{
  201. Duration: config.Accounts.LoginThrottling.Duration,
  202. Limit: config.Accounts.LoginThrottling.MaxAttempts,
  203. },
  204. server: server,
  205. accountName: "*",
  206. nick: "*", // * is used until actual nick is given
  207. nickCasefolded: "*",
  208. nickMaskString: "*", // * is used until actual nick is given
  209. }
  210. client.history.Initialize(config.History.ClientLength, config.History.AutoresizeWindow)
  211. client.brbTimer.Initialize(client)
  212. session := &Session{
  213. client: client,
  214. socket: socket,
  215. capVersion: caps.Cap301,
  216. capState: caps.NoneState,
  217. ctime: now,
  218. atime: now,
  219. realIP: realIP,
  220. }
  221. session.SetMaxlenRest()
  222. client.sessions = []*Session{session}
  223. if conn.Config.TLSConfig != nil {
  224. client.SetMode(modes.TLS, true)
  225. // error is not useful to us here anyways so we can ignore it
  226. client.certfp, _ = socket.CertFP()
  227. }
  228. if conn.Config.IsTor {
  229. client.SetMode(modes.TLS, true)
  230. // cover up details of the tor proxying infrastructure (not a user privacy concern,
  231. // but a hardening measure):
  232. session.proxiedIP = utils.IPv4LoopbackAddress
  233. session.rawHostname = config.Server.TorListeners.Vhost
  234. } else {
  235. // set the hostname for this client (may be overridden later by PROXY or WEBIRC)
  236. session.rawHostname = utils.LookupHostname(session.realIP.String())
  237. client.cloakedHostname = config.Server.Cloaks.ComputeCloak(session.realIP)
  238. remoteAddr := conn.Conn.RemoteAddr()
  239. if utils.AddrIsLocal(remoteAddr) {
  240. // treat local connections as secure (may be overridden later by WEBIRC)
  241. client.SetMode(modes.TLS, true)
  242. }
  243. if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
  244. client.doIdentLookup(conn.Conn)
  245. }
  246. }
  247. client.realIP = session.realIP
  248. client.rawHostname = session.rawHostname
  249. client.proxiedIP = session.proxiedIP
  250. server.stats.Add()
  251. client.run(session)
  252. }
  253. func (client *Client) doIdentLookup(conn net.Conn) {
  254. _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
  255. if err != nil {
  256. client.server.logger.Error("internal", "bad server address", err.Error())
  257. return
  258. }
  259. serverPort, _ := strconv.Atoi(serverPortString)
  260. clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String())
  261. if err != nil {
  262. client.server.logger.Error("internal", "bad client address", err.Error())
  263. return
  264. }
  265. clientPort, _ := strconv.Atoi(clientPortString)
  266. client.Notice(client.t("*** Looking up your username"))
  267. resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
  268. if err == nil {
  269. err := client.SetNames(resp.Identifier, "", true)
  270. if err == nil {
  271. client.Notice(client.t("*** Found your username"))
  272. // we don't need to updateNickMask here since nickMask is not used for anything yet
  273. } else {
  274. client.Notice(client.t("*** Got a malformed username, ignoring"))
  275. }
  276. } else {
  277. client.Notice(client.t("*** Could not find your username"))
  278. }
  279. }
  280. type AuthOutcome uint
  281. const (
  282. authSuccess AuthOutcome = iota
  283. authFailPass
  284. authFailTorSaslRequired
  285. authFailSaslRequired
  286. )
  287. func (client *Client) isAuthorized(config *Config) AuthOutcome {
  288. saslSent := client.account != ""
  289. // PASS requirement
  290. if (config.Server.passwordBytes != nil) && !client.sentPassCommand && !(config.Accounts.SkipServerPassword && saslSent) {
  291. return authFailPass
  292. }
  293. // Tor connections may be required to authenticate with SASL
  294. if client.isTor && config.Server.TorListeners.RequireSasl && !saslSent {
  295. return authFailTorSaslRequired
  296. }
  297. // finally, enforce require-sasl
  298. if config.Accounts.RequireSasl.Enabled && !saslSent && !utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets) {
  299. return authFailSaslRequired
  300. }
  301. return authSuccess
  302. }
  303. func (session *Session) resetFakelag() {
  304. var flc FakelagConfig = session.client.server.Config().Fakelag
  305. flc.Enabled = flc.Enabled && !session.client.HasRoleCapabs("nofakelag")
  306. session.fakelag.Initialize(flc)
  307. }
  308. // IP returns the IP address of this client.
  309. func (client *Client) IP() net.IP {
  310. client.stateMutex.RLock()
  311. defer client.stateMutex.RUnlock()
  312. if client.proxiedIP != nil {
  313. return client.proxiedIP
  314. }
  315. return client.realIP
  316. }
  317. // IPString returns the IP address of this client as a string.
  318. func (client *Client) IPString() string {
  319. ip := client.IP().String()
  320. if 0 < len(ip) && ip[0] == ':' {
  321. ip = "0" + ip
  322. }
  323. return ip
  324. }
  325. // t returns the translated version of the given string, based on the languages configured by the client.
  326. func (client *Client) t(originalString string) string {
  327. languageManager := client.server.Config().languageManager
  328. if !languageManager.Enabled() {
  329. return originalString
  330. }
  331. return languageManager.Translate(client.Languages(), originalString)
  332. }
  333. //
  334. // command goroutine
  335. //
  336. func (client *Client) run(session *Session) {
  337. defer func() {
  338. if r := recover(); r != nil {
  339. client.server.logger.Error("internal",
  340. fmt.Sprintf("Client caused panic: %v\n%s", r, debug.Stack()))
  341. if client.server.Config().Debug.recoverFromErrors {
  342. client.server.logger.Error("internal", "Disconnecting client and attempting to recover")
  343. } else {
  344. panic(r)
  345. }
  346. }
  347. // ensure client connection gets closed
  348. client.destroy(session)
  349. }()
  350. session.idletimer.Initialize(session)
  351. session.resetFakelag()
  352. isReattach := client.Registered()
  353. if isReattach {
  354. if session.resumeDetails != nil {
  355. session.playResume()
  356. session.resumeDetails = nil
  357. client.brbTimer.Disable()
  358. client.SetAway(false, "") // clear BRB message if any
  359. } else {
  360. client.playReattachMessages(session)
  361. }
  362. } else {
  363. // don't reset the nick timer during a reattach
  364. client.nickTimer.Initialize(client)
  365. }
  366. firstLine := !isReattach
  367. for {
  368. maxlenRest := session.MaxlenRest()
  369. line, err := session.socket.Read()
  370. if err != nil {
  371. quitMessage := "connection closed"
  372. if err == errReadQ {
  373. quitMessage = "readQ exceeded"
  374. }
  375. client.Quit(quitMessage, session)
  376. // since the client did not actually send us a QUIT,
  377. // give them a chance to resume if applicable:
  378. if !session.Destroyed() {
  379. client.brbTimer.Enable()
  380. }
  381. break
  382. }
  383. if client.server.logger.IsLoggingRawIO() {
  384. client.server.logger.Debug("userinput", client.nick, "<- ", line)
  385. }
  386. // special-cased handling of PROXY protocol, see `handleProxyCommand` for details:
  387. if firstLine {
  388. firstLine = false
  389. if strings.HasPrefix(line, "PROXY") {
  390. err = handleProxyCommand(client.server, client, session, line)
  391. if err != nil {
  392. break
  393. } else {
  394. continue
  395. }
  396. }
  397. }
  398. if client.registered {
  399. session.fakelag.Touch()
  400. } else {
  401. // DoS hardening, #505
  402. session.registrationMessages++
  403. if client.server.Config().Limits.RegistrationMessages < session.registrationMessages {
  404. client.Send(nil, client.server.name, ERR_UNKNOWNERROR, "*", client.t("You have sent too many registration messages"))
  405. break
  406. }
  407. }
  408. msg, err := ircmsg.ParseLineStrict(line, true, maxlenRest)
  409. if err == ircmsg.ErrorLineIsEmpty {
  410. continue
  411. } else if err == ircmsg.ErrorLineTooLong {
  412. session.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.Nick(), client.t("Input line too long"))
  413. continue
  414. } else if err != nil {
  415. client.Quit(client.t("Received malformed line"), session)
  416. break
  417. }
  418. cmd, exists := Commands[msg.Command]
  419. if !exists {
  420. if len(msg.Command) > 0 {
  421. session.Send(nil, client.server.name, ERR_UNKNOWNCOMMAND, client.Nick(), msg.Command, client.t("Unknown command"))
  422. } else {
  423. session.Send(nil, client.server.name, ERR_UNKNOWNCOMMAND, client.Nick(), "lastcmd", client.t("No command given"))
  424. }
  425. continue
  426. }
  427. isExiting := cmd.Run(client.server, client, session, msg)
  428. if isExiting {
  429. break
  430. } else if session.client != client {
  431. // bouncer reattach
  432. go session.client.run(session)
  433. break
  434. }
  435. }
  436. }
  437. func (client *Client) playReattachMessages(session *Session) {
  438. client.server.playRegistrationBurst(session)
  439. for _, channel := range session.client.Channels() {
  440. channel.playJoinForSession(session)
  441. // clients should receive autoreplay-on-join lines, if applicable;
  442. // if they negotiated znc.in/playback or chathistory, they will receive nothing,
  443. // because those caps disable autoreplay-on-join and they haven't sent the relevant
  444. // *playback PRIVMSG or CHATHISTORY command yet
  445. rb := NewResponseBuffer(session)
  446. channel.autoReplayHistory(client, rb, "")
  447. rb.Send(true)
  448. }
  449. }
  450. //
  451. // idle, quit, timers and timeouts
  452. //
  453. // Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
  454. func (client *Client) Active(session *Session) {
  455. now := time.Now().UTC()
  456. client.stateMutex.Lock()
  457. defer client.stateMutex.Unlock()
  458. session.atime = now
  459. client.atime = now
  460. }
  461. // Ping sends the client a PING message.
  462. func (session *Session) Ping() {
  463. session.Send(nil, "", "PING", session.client.Nick())
  464. }
  465. // tryResume tries to resume if the client asked us to.
  466. func (session *Session) tryResume() (success bool) {
  467. var oldResumeID string
  468. defer func() {
  469. if success {
  470. // "On a successful request, the server [...] terminates the old client's connection"
  471. oldSession := session.client.GetSessionByResumeID(oldResumeID)
  472. if oldSession != nil {
  473. session.client.destroy(oldSession)
  474. }
  475. } else {
  476. session.resumeDetails = nil
  477. }
  478. }()
  479. client := session.client
  480. server := client.server
  481. config := server.Config()
  482. oldClient, oldResumeID := server.resumeManager.VerifyToken(client, session.resumeDetails.PresentedToken)
  483. if oldClient == nil {
  484. session.Send(nil, server.name, "FAIL", "RESUME", "INVALID_TOKEN", client.t("Cannot resume connection, token is not valid"))
  485. return
  486. }
  487. resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
  488. if !resumeAllowed {
  489. session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection, old and new clients must have TLS"))
  490. return
  491. }
  492. if oldClient.isTor != client.isTor {
  493. session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection from Tor to non-Tor or vice versa"))
  494. return
  495. }
  496. err := server.clients.Resume(oldClient, session)
  497. if err != nil {
  498. session.Send(nil, server.name, "FAIL", "RESUME", "CANNOT_RESUME", client.t("Cannot resume connection"))
  499. return
  500. }
  501. success = true
  502. client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", oldClient.Nick()))
  503. return
  504. }
  505. // playResume is called from the session's fresh goroutine after a resume;
  506. // it sends notifications to friends, then plays the registration burst and replays
  507. // stored history to the session
  508. func (session *Session) playResume() {
  509. client := session.client
  510. server := client.server
  511. friends := make(ClientSet)
  512. oldestLostMessage := time.Now().UTC()
  513. // work out how much time, if any, is not covered by history buffers
  514. for _, channel := range client.Channels() {
  515. for _, member := range channel.Members() {
  516. friends.Add(member)
  517. lastDiscarded := channel.history.LastDiscarded()
  518. if lastDiscarded.Before(oldestLostMessage) {
  519. oldestLostMessage = lastDiscarded
  520. }
  521. }
  522. }
  523. privmsgMatcher := func(item history.Item) bool {
  524. return item.Type == history.Privmsg || item.Type == history.Notice || item.Type == history.Tagmsg
  525. }
  526. privmsgHistory := client.history.Match(privmsgMatcher, false, 0)
  527. lastDiscarded := client.history.LastDiscarded()
  528. if lastDiscarded.Before(oldestLostMessage) {
  529. oldestLostMessage = lastDiscarded
  530. }
  531. for _, item := range privmsgHistory {
  532. sender := server.clients.Get(stripMaskFromNick(item.Nick))
  533. if sender != nil {
  534. friends.Add(sender)
  535. }
  536. }
  537. timestamp := session.resumeDetails.Timestamp
  538. gap := lastDiscarded.Sub(timestamp)
  539. session.resumeDetails.HistoryIncomplete = gap > 0 || timestamp.IsZero()
  540. gapSeconds := int(gap.Seconds()) + 1 // round up to avoid confusion
  541. details := client.Details()
  542. oldNickmask := details.nickMask
  543. client.SetRawHostname(session.rawHostname)
  544. hostname := client.Hostname() // may be a vhost
  545. timestampString := timestamp.Format(IRCv3TimestampFormat)
  546. // send quit/resume messages to friends
  547. for friend := range friends {
  548. if friend == client {
  549. continue
  550. }
  551. for _, fSession := range friend.Sessions() {
  552. if fSession.capabilities.Has(caps.Resume) {
  553. if !session.resumeDetails.HistoryIncomplete {
  554. fSession.Send(nil, oldNickmask, "RESUMED", hostname, "ok")
  555. } else if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() {
  556. fSession.Send(nil, oldNickmask, "RESUMED", hostname, timestampString)
  557. } else {
  558. fSession.Send(nil, oldNickmask, "RESUMED", hostname)
  559. }
  560. } else {
  561. if !session.resumeDetails.HistoryIncomplete {
  562. fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
  563. } else if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() {
  564. fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of message history lost)"), gapSeconds))
  565. } else {
  566. fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (message history may have been lost)")))
  567. }
  568. }
  569. }
  570. }
  571. if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() {
  572. session.Send(nil, client.server.name, "WARN", "RESUME", "HISTORY_LOST", fmt.Sprintf(client.t("Resume may have lost up to %d seconds of history"), gapSeconds))
  573. } else {
  574. session.Send(nil, client.server.name, "WARN", "RESUME", "HISTORY_LOST", client.t("Resume may have lost some message history"))
  575. }
  576. session.Send(nil, client.server.name, "RESUME", "SUCCESS", details.nick)
  577. server.playRegistrationBurst(session)
  578. for _, channel := range client.Channels() {
  579. channel.Resume(session, timestamp)
  580. }
  581. // replay direct PRIVSMG history
  582. if !timestamp.IsZero() {
  583. now := time.Now().UTC()
  584. items, complete := client.history.Between(timestamp, now, false, 0)
  585. rb := NewResponseBuffer(client.Sessions()[0])
  586. client.replayPrivmsgHistory(rb, items, complete)
  587. rb.Send(true)
  588. }
  589. session.resumeDetails = nil
  590. }
  591. func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
  592. var batchID string
  593. details := client.Details()
  594. nick := details.nick
  595. if 0 < len(items) {
  596. batchID = rb.StartNestedHistoryBatch(nick)
  597. }
  598. allowTags := rb.session.capabilities.Has(caps.MessageTags)
  599. for _, item := range items {
  600. var command string
  601. switch item.Type {
  602. case history.Privmsg:
  603. command = "PRIVMSG"
  604. case history.Notice:
  605. command = "NOTICE"
  606. case history.Tagmsg:
  607. if allowTags {
  608. command = "TAGMSG"
  609. } else {
  610. continue
  611. }
  612. default:
  613. continue
  614. }
  615. var tags map[string]string
  616. if allowTags {
  617. tags = item.Tags
  618. }
  619. if item.Params[0] == "" {
  620. // this message was sent *to* the client from another nick
  621. rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
  622. } else {
  623. // this message was sent *from* the client to another nick; the target is item.Params[0]
  624. // substitute the client's current nickmask in case they changed nick
  625. rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, tags, command, item.Params[0], item.Message)
  626. }
  627. }
  628. rb.EndNestedBatch(batchID)
  629. if !complete {
  630. rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost"))
  631. }
  632. }
  633. // IdleTime returns how long this client's been idle.
  634. func (client *Client) IdleTime() time.Duration {
  635. client.stateMutex.RLock()
  636. defer client.stateMutex.RUnlock()
  637. return time.Since(client.atime)
  638. }
  639. // SignonTime returns this client's signon time as a unix timestamp.
  640. func (client *Client) SignonTime() int64 {
  641. return client.ctime.Unix()
  642. }
  643. // IdleSeconds returns the number of seconds this client's been idle.
  644. func (client *Client) IdleSeconds() uint64 {
  645. return uint64(client.IdleTime().Seconds())
  646. }
  647. // HasNick returns true if the client's nickname is set (used in registration).
  648. func (client *Client) HasNick() bool {
  649. client.stateMutex.RLock()
  650. defer client.stateMutex.RUnlock()
  651. return client.nick != "" && client.nick != "*"
  652. }
  653. // HasUsername returns true if the client's username is set (used in registration).
  654. func (client *Client) HasUsername() bool {
  655. client.stateMutex.RLock()
  656. defer client.stateMutex.RUnlock()
  657. return client.username != "" && client.username != "*"
  658. }
  659. // SetNames sets the client's ident and realname.
  660. func (client *Client) SetNames(username, realname string, fromIdent bool) error {
  661. limit := client.server.Config().Limits.IdentLen
  662. if !fromIdent {
  663. limit -= 1 // leave room for the prepended ~
  664. }
  665. if limit < len(username) {
  666. username = username[:limit]
  667. }
  668. if !isIdent(username) {
  669. return errInvalidUsername
  670. }
  671. if !fromIdent {
  672. username = "~" + username
  673. }
  674. client.stateMutex.Lock()
  675. defer client.stateMutex.Unlock()
  676. if client.username == "" {
  677. client.username = username
  678. }
  679. if client.realname == "" {
  680. client.realname = realname
  681. }
  682. return nil
  683. }
  684. // HasRoleCapabs returns true if client has the given (role) capabilities.
  685. func (client *Client) HasRoleCapabs(capabs ...string) bool {
  686. oper := client.Oper()
  687. if oper == nil {
  688. return false
  689. }
  690. for _, capab := range capabs {
  691. if !oper.Class.Capabilities[capab] {
  692. return false
  693. }
  694. }
  695. return true
  696. }
  697. // ModeString returns the mode string for this client.
  698. func (client *Client) ModeString() (str string) {
  699. return "+" + client.flags.String()
  700. }
  701. // Friends refers to clients that share a channel with this client.
  702. func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]bool) {
  703. result = make(map[*Session]bool)
  704. // look at the client's own sessions
  705. for _, session := range client.Sessions() {
  706. if session.capabilities.HasAll(capabs...) {
  707. result[session] = true
  708. }
  709. }
  710. for _, channel := range client.Channels() {
  711. for _, member := range channel.Members() {
  712. for _, session := range member.Sessions() {
  713. if session.capabilities.HasAll(capabs...) {
  714. result[session] = true
  715. }
  716. }
  717. }
  718. }
  719. return
  720. }
  721. func (client *Client) SetOper(oper *Oper) {
  722. client.stateMutex.Lock()
  723. defer client.stateMutex.Unlock()
  724. client.oper = oper
  725. // operators typically get a vhost, update the nickmask
  726. client.updateNickMaskNoMutex()
  727. }
  728. // XXX: CHGHOST requires prefix nickmask to have original hostname,
  729. // this is annoying to do correctly
  730. func (client *Client) sendChghost(oldNickMask string, vhost string) {
  731. username := client.Username()
  732. for fClient := range client.Friends(caps.ChgHost) {
  733. fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, client.AccountName(), nil, "CHGHOST", username, vhost)
  734. }
  735. }
  736. // choose the correct vhost to display
  737. func (client *Client) getVHostNoMutex() string {
  738. // hostserv vhost OR operclass vhost OR nothing (i.e., normal rdns hostmask)
  739. if client.vhost != "" {
  740. return client.vhost
  741. } else if client.oper != nil {
  742. return client.oper.Vhost
  743. } else {
  744. return ""
  745. }
  746. }
  747. // SetVHost updates the client's hostserv-based vhost
  748. func (client *Client) SetVHost(vhost string) (updated bool) {
  749. client.stateMutex.Lock()
  750. defer client.stateMutex.Unlock()
  751. updated = (client.vhost != vhost)
  752. client.vhost = vhost
  753. if updated {
  754. client.updateNickMaskNoMutex()
  755. }
  756. return
  757. }
  758. // updateNick updates `nick` and `nickCasefolded`.
  759. func (client *Client) updateNick(nick, nickCasefolded, skeleton string) {
  760. client.stateMutex.Lock()
  761. defer client.stateMutex.Unlock()
  762. client.nick = nick
  763. client.nickCasefolded = nickCasefolded
  764. client.skeleton = skeleton
  765. client.updateNickMaskNoMutex()
  766. }
  767. // updateNickMaskNoMutex updates the casefolded nickname and nickmask, not acquiring any mutexes.
  768. func (client *Client) updateNickMaskNoMutex() {
  769. client.hostname = client.getVHostNoMutex()
  770. if client.hostname == "" {
  771. client.hostname = client.cloakedHostname
  772. if client.hostname == "" {
  773. client.hostname = client.rawHostname
  774. }
  775. }
  776. cfhostname, err := Casefold(client.hostname)
  777. if err != nil {
  778. client.server.logger.Error("internal", "hostname couldn't be casefolded", client.hostname, err.Error())
  779. cfhostname = client.hostname // YOLO
  780. }
  781. client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.hostname)
  782. client.nickMaskCasefolded = fmt.Sprintf("%s!%s@%s", client.nickCasefolded, strings.ToLower(client.username), cfhostname)
  783. }
  784. // AllNickmasks returns all the possible nickmasks for the client.
  785. func (client *Client) AllNickmasks() (masks []string) {
  786. client.stateMutex.RLock()
  787. nick := client.nickCasefolded
  788. username := client.username
  789. rawHostname := client.rawHostname
  790. cloakedHostname := client.cloakedHostname
  791. vhost := client.getVHostNoMutex()
  792. client.stateMutex.RUnlock()
  793. username = strings.ToLower(username)
  794. if len(vhost) > 0 {
  795. cfvhost, err := Casefold(vhost)
  796. if err == nil {
  797. masks = append(masks, fmt.Sprintf("%s!%s@%s", nick, username, cfvhost))
  798. }
  799. }
  800. var rawhostmask string
  801. cfrawhost, err := Casefold(rawHostname)
  802. if err == nil {
  803. rawhostmask = fmt.Sprintf("%s!%s@%s", nick, username, cfrawhost)
  804. masks = append(masks, rawhostmask)
  805. }
  806. if cloakedHostname != "" {
  807. masks = append(masks, fmt.Sprintf("%s!%s@%s", nick, username, cloakedHostname))
  808. }
  809. ipmask := fmt.Sprintf("%s!%s@%s", nick, username, client.IPString())
  810. if ipmask != rawhostmask {
  811. masks = append(masks, ipmask)
  812. }
  813. return
  814. }
  815. // LoggedIntoAccount returns true if this client is logged into an account.
  816. func (client *Client) LoggedIntoAccount() bool {
  817. return client.Account() != ""
  818. }
  819. // Quit sets the given quit message for the client.
  820. // (You must ensure separately that destroy() is called, e.g., by returning `true` from
  821. // the command handler or calling it yourself.)
  822. func (client *Client) Quit(message string, session *Session) {
  823. setFinalData := func(sess *Session) {
  824. message := sess.quitMessage
  825. var finalData []byte
  826. // #364: don't send QUIT lines to unregistered clients
  827. if client.registered {
  828. quitMsg := ircmsg.MakeMessage(nil, client.nickMaskString, "QUIT", message)
  829. finalData, _ = quitMsg.LineBytesStrict(false, 512)
  830. }
  831. errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
  832. errorMsgBytes, _ := errorMsg.LineBytesStrict(false, 512)
  833. finalData = append(finalData, errorMsgBytes...)
  834. sess.socket.SetFinalData(finalData)
  835. }
  836. client.stateMutex.Lock()
  837. defer client.stateMutex.Unlock()
  838. var sessions []*Session
  839. if session != nil {
  840. sessions = []*Session{session}
  841. } else {
  842. sessions = client.sessions
  843. }
  844. for _, session := range sessions {
  845. if session.SetQuitMessage(message) {
  846. setFinalData(session)
  847. }
  848. }
  849. }
  850. // destroy gets rid of a client, removes them from server lists etc.
  851. // if `session` is nil, destroys the client unconditionally, removing all sessions;
  852. // otherwise, destroys one specific session, only destroying the client if it
  853. // has no more sessions.
  854. func (client *Client) destroy(session *Session) {
  855. var sessionsToDestroy []*Session
  856. client.stateMutex.Lock()
  857. details := client.detailsNoMutex()
  858. brbState := client.brbTimer.state
  859. brbAt := client.brbTimer.brbAt
  860. wasReattach := session != nil && session.client != client
  861. sessionRemoved := false
  862. var remainingSessions int
  863. if session == nil {
  864. sessionsToDestroy = client.sessions
  865. client.sessions = nil
  866. remainingSessions = 0
  867. } else {
  868. sessionRemoved, remainingSessions = client.removeSession(session)
  869. if sessionRemoved {
  870. sessionsToDestroy = []*Session{session}
  871. }
  872. }
  873. // should we destroy the whole client this time?
  874. // BRB is not respected if this is a destroy of the whole client (i.e., session == nil)
  875. brbEligible := session != nil && (brbState == BrbEnabled || brbState == BrbSticky)
  876. shouldDestroy := !client.destroyed && remainingSessions == 0 && !brbEligible
  877. if shouldDestroy {
  878. // if it's our job to destroy it, don't let anyone else try
  879. client.destroyed = true
  880. }
  881. exitedSnomaskSent := client.exitedSnomaskSent
  882. client.stateMutex.Unlock()
  883. // destroy all applicable sessions:
  884. var quitMessage string
  885. for _, session := range sessionsToDestroy {
  886. if session.client != client {
  887. // session has been attached to a new client; do not destroy it
  888. continue
  889. }
  890. session.idletimer.Stop()
  891. // send quit/error message to client if they haven't been sent already
  892. client.Quit("", session)
  893. quitMessage = session.quitMessage
  894. session.SetDestroyed()
  895. session.socket.Close()
  896. // remove from connection limits
  897. var source string
  898. if client.isTor {
  899. client.server.torLimiter.RemoveClient()
  900. source = "tor"
  901. } else {
  902. ip := session.realIP
  903. if session.proxiedIP != nil {
  904. ip = session.proxiedIP
  905. }
  906. client.server.connectionLimiter.RemoveClient(ip)
  907. source = ip.String()
  908. }
  909. client.server.logger.Info("localconnect-ip", fmt.Sprintf("disconnecting session of %s from %s", details.nick, source))
  910. }
  911. // do not destroy the client if it has either remaining sessions, or is BRB'ed
  912. if !shouldDestroy {
  913. return
  914. }
  915. // see #235: deduplicating the list of PART recipients uses (comparatively speaking)
  916. // a lot of RAM, so limit concurrency to avoid thrashing
  917. client.server.semaphores.ClientDestroy.Acquire()
  918. defer client.server.semaphores.ClientDestroy.Release()
  919. if !wasReattach {
  920. client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", details.nick))
  921. }
  922. registered := client.Registered()
  923. if registered {
  924. client.server.whoWas.Append(client.WhoWas())
  925. }
  926. client.server.resumeManager.Delete(client)
  927. // alert monitors
  928. if registered {
  929. client.server.monitorManager.AlertAbout(client, false)
  930. }
  931. // clean up monitor state
  932. client.server.monitorManager.RemoveAll(client)
  933. splitQuitMessage := utils.MakeSplitMessage(quitMessage, true)
  934. // clean up channels
  935. // (note that if this is a reattach, client has no channels and therefore no friends)
  936. friends := make(ClientSet)
  937. for _, channel := range client.Channels() {
  938. channel.Quit(client)
  939. channel.history.Add(history.Item{
  940. Type: history.Quit,
  941. Nick: details.nickMask,
  942. AccountName: details.accountName,
  943. Message: splitQuitMessage,
  944. })
  945. for _, member := range channel.Members() {
  946. friends.Add(member)
  947. }
  948. }
  949. friends.Remove(client)
  950. // clean up server
  951. client.server.clients.Remove(client)
  952. // clean up self
  953. client.nickTimer.Stop()
  954. client.brbTimer.Disable()
  955. client.server.accounts.Logout(client)
  956. client.server.stats.Remove(registered, client.HasMode(modes.Invisible),
  957. client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator))
  958. // this happens under failure to return from BRB
  959. if quitMessage == "" {
  960. if brbState == BrbDead && !brbAt.IsZero() {
  961. awayMessage := client.AwayMessage()
  962. if awayMessage == "" {
  963. awayMessage = "Disconnected" // auto-BRB
  964. }
  965. quitMessage = fmt.Sprintf("%s [%s ago]", awayMessage, time.Since(brbAt).Truncate(time.Second).String())
  966. }
  967. }
  968. if quitMessage == "" {
  969. quitMessage = "Exited"
  970. }
  971. for friend := range friends {
  972. friend.sendFromClientInternal(false, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
  973. }
  974. if !exitedSnomaskSent && registered {
  975. client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), details.nick))
  976. }
  977. }
  978. // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
  979. // Adds account-tag to the line as well.
  980. func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
  981. if session.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
  982. session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
  983. } else {
  984. for _, messagePair := range message.Wrapped {
  985. session.sendFromClientInternal(blocking, message.Time, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
  986. }
  987. }
  988. }
  989. // Sends a line with `nickmask` as the prefix, adding `time` and `account` tags if supported
  990. func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
  991. for _, session := range client.Sessions() {
  992. err_ := session.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, params...)
  993. if err_ != nil {
  994. err = err_
  995. }
  996. }
  997. return
  998. }
  999. func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
  1000. msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
  1001. // attach account-tag
  1002. if session.capabilities.Has(caps.AccountTag) && accountName != "*" {
  1003. msg.SetTag("account", accountName)
  1004. }
  1005. // attach message-id
  1006. if msgid != "" && session.capabilities.Has(caps.MessageTags) {
  1007. msg.SetTag("msgid", msgid)
  1008. }
  1009. // attach server-time
  1010. session.setTimeTag(&msg, serverTime)
  1011. return session.SendRawMessage(msg, blocking)
  1012. }
  1013. var (
  1014. // these are all the output commands that MUST have their last param be a trailing.
  1015. // this is needed because dumb clients like to treat trailing params separately from the
  1016. // other params in messages.
  1017. commandsThatMustUseTrailing = map[string]bool{
  1018. "PRIVMSG": true,
  1019. "NOTICE": true,
  1020. RPL_WHOISCHANNELS: true,
  1021. RPL_USERHOST: true,
  1022. }
  1023. )
  1024. // SendRawMessage sends a raw message to the client.
  1025. func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
  1026. // use dumb hack to force the last param to be a trailing param if required
  1027. var usedTrailingHack bool
  1028. config := session.client.server.Config()
  1029. if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
  1030. lastParam := message.Params[len(message.Params)-1]
  1031. // to force trailing, we ensure the final param contains a space
  1032. if strings.IndexByte(lastParam, ' ') == -1 {
  1033. message.Params[len(message.Params)-1] = lastParam + " "
  1034. usedTrailingHack = true
  1035. }
  1036. }
  1037. // assemble message
  1038. maxlenRest := session.MaxlenRest()
  1039. line, err := message.LineBytesStrict(false, maxlenRest)
  1040. if err != nil {
  1041. logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
  1042. session.client.server.logger.Error("internal", logline)
  1043. message = ircmsg.MakeMessage(nil, session.client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
  1044. line, _ := message.LineBytesStrict(false, 0)
  1045. if blocking {
  1046. session.socket.BlockingWrite(line)
  1047. } else {
  1048. session.socket.Write(line)
  1049. }
  1050. return err
  1051. }
  1052. // if we used the trailing hack, we need to strip the final space we appended earlier on
  1053. if usedTrailingHack {
  1054. copy(line[len(line)-3:], "\r\n")
  1055. line = line[:len(line)-1]
  1056. }
  1057. if session.client.server.logger.IsLoggingRawIO() {
  1058. logline := string(line[:len(line)-2]) // strip "\r\n"
  1059. session.client.server.logger.Debug("useroutput", session.client.Nick(), " ->", logline)
  1060. }
  1061. if blocking {
  1062. return session.socket.BlockingWrite(line)
  1063. } else {
  1064. return session.socket.Write(line)
  1065. }
  1066. }
  1067. // Send sends an IRC line to the client.
  1068. func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
  1069. for _, session := range client.Sessions() {
  1070. err_ := session.Send(tags, prefix, command, params...)
  1071. if err_ != nil {
  1072. err = err_
  1073. }
  1074. }
  1075. return
  1076. }
  1077. func (session *Session) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
  1078. msg := ircmsg.MakeMessage(tags, prefix, command, params...)
  1079. session.setTimeTag(&msg, time.Time{})
  1080. return session.SendRawMessage(msg, false)
  1081. }
  1082. func (session *Session) setTimeTag(msg *ircmsg.IrcMessage, serverTime time.Time) {
  1083. if session.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
  1084. if serverTime.IsZero() {
  1085. serverTime = time.Now()
  1086. }
  1087. msg.SetTag("time", serverTime.UTC().Format(IRCv3TimestampFormat))
  1088. }
  1089. }
  1090. // Notice sends the client a notice from the server.
  1091. func (client *Client) Notice(text string) {
  1092. client.Send(nil, client.server.name, "NOTICE", client.Nick(), text)
  1093. }
  1094. func (client *Client) addChannel(channel *Channel) {
  1095. client.stateMutex.Lock()
  1096. client.channels[channel] = true
  1097. client.stateMutex.Unlock()
  1098. }
  1099. func (client *Client) removeChannel(channel *Channel) {
  1100. client.stateMutex.Lock()
  1101. delete(client.channels, channel)
  1102. client.stateMutex.Unlock()
  1103. }
  1104. // Records that the client has been invited to join an invite-only channel
  1105. func (client *Client) Invite(casefoldedChannel string) {
  1106. client.stateMutex.Lock()
  1107. defer client.stateMutex.Unlock()
  1108. if client.invitedTo == nil {
  1109. client.invitedTo = make(map[string]bool)
  1110. }
  1111. client.invitedTo[casefoldedChannel] = true
  1112. }
  1113. // Checks that the client was invited to join a given channel
  1114. func (client *Client) CheckInvited(casefoldedChannel string) (invited bool) {
  1115. client.stateMutex.Lock()
  1116. defer client.stateMutex.Unlock()
  1117. invited = client.invitedTo[casefoldedChannel]
  1118. // joining an invited channel "uses up" your invite, so you can't rejoin on kick
  1119. delete(client.invitedTo, casefoldedChannel)
  1120. return
  1121. }