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 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  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. "errors"
  8. "fmt"
  9. "log"
  10. "net"
  11. "runtime/debug"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "sync/atomic"
  16. "time"
  17. "github.com/goshuirc/irc-go/ircfmt"
  18. "github.com/goshuirc/irc-go/ircmsg"
  19. ident "github.com/oragono/go-ident"
  20. "github.com/oragono/oragono/irc/caps"
  21. "github.com/oragono/oragono/irc/sno"
  22. "github.com/oragono/oragono/irc/utils"
  23. )
  24. const (
  25. // IdentTimeoutSeconds is how many seconds before our ident (username) check times out.
  26. IdentTimeoutSeconds = 1.5
  27. )
  28. var (
  29. // ErrNickAlreadySet is a weird error that's sent when the server's consistency has been compromised.
  30. ErrNickAlreadySet = errors.New("Nickname is already set")
  31. )
  32. // Client is an IRC client.
  33. type Client struct {
  34. account *ClientAccount
  35. atime time.Time
  36. authorized bool
  37. awayMessage string
  38. capabilities *caps.Set
  39. capState CapState
  40. capVersion caps.Version
  41. certfp string
  42. channels ChannelSet
  43. class *OperClass
  44. ctime time.Time
  45. exitedSnomaskSent bool
  46. flags map[Mode]bool
  47. hasQuit bool
  48. hops int
  49. hostname string
  50. idletimer *IdleTimer
  51. isDestroyed bool
  52. isQuitting bool
  53. maxlenTags uint32
  54. maxlenRest uint32
  55. nick string
  56. nickCasefolded string
  57. nickMaskCasefolded string
  58. nickMaskString string // cache for nickmask string since it's used with lots of replies
  59. operName string
  60. proxiedIP string // actual remote IP if using the PROXY protocol
  61. quitMessage string
  62. rawHostname string
  63. realname string
  64. registered bool
  65. saslInProgress bool
  66. saslMechanism string
  67. saslValue string
  68. server *Server
  69. socket *Socket
  70. stateMutex sync.RWMutex // generic protection for mutable state
  71. username string
  72. vhost string
  73. whoisLine string
  74. }
  75. // NewClient returns a client with all the appropriate info setup.
  76. func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
  77. now := time.Now()
  78. socket := NewSocket(conn, server.MaxSendQBytes)
  79. go socket.RunSocketWriter()
  80. client := &Client{
  81. atime: now,
  82. authorized: server.getPassword() == nil,
  83. capabilities: caps.NewSet(),
  84. capState: CapNone,
  85. capVersion: caps.Cap301,
  86. channels: make(ChannelSet),
  87. ctime: now,
  88. flags: make(map[Mode]bool),
  89. server: server,
  90. socket: &socket,
  91. account: &NoAccount,
  92. nick: "*", // * is used until actual nick is given
  93. nickCasefolded: "*",
  94. nickMaskString: "*", // * is used until actual nick is given
  95. }
  96. client.recomputeMaxlens()
  97. if isTLS {
  98. client.flags[TLS] = true
  99. // error is not useful to us here anyways so we can ignore it
  100. client.certfp, _ = client.socket.CertFP()
  101. }
  102. if server.checkIdent {
  103. _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
  104. serverPort, _ := strconv.Atoi(serverPortString)
  105. if err != nil {
  106. log.Fatal(err)
  107. }
  108. clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String())
  109. clientPort, _ := strconv.Atoi(clientPortString)
  110. if err != nil {
  111. log.Fatal(err)
  112. }
  113. client.Notice("*** Looking up your username")
  114. resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
  115. if err == nil {
  116. username := resp.Identifier
  117. _, err := CasefoldName(username) // ensure it's a valid username
  118. if err == nil {
  119. client.Notice("*** Found your username")
  120. client.username = username
  121. // we don't need to updateNickMask here since nickMask is not used for anything yet
  122. } else {
  123. client.Notice("*** Got a malformed username, ignoring")
  124. }
  125. } else {
  126. client.Notice("*** Could not find your username")
  127. }
  128. }
  129. go client.run()
  130. return client
  131. }
  132. // IP returns the IP address of this client.
  133. func (client *Client) IP() net.IP {
  134. if client.proxiedIP != "" {
  135. return net.ParseIP(client.proxiedIP)
  136. }
  137. return net.ParseIP(utils.IPString(client.socket.conn.RemoteAddr()))
  138. }
  139. // IPString returns the IP address of this client as a string.
  140. func (client *Client) IPString() string {
  141. if client.proxiedIP != "" {
  142. return client.proxiedIP
  143. }
  144. ip := client.IP().String()
  145. if 0 < len(ip) && ip[0] == ':' {
  146. ip = "0" + ip
  147. }
  148. return ip
  149. }
  150. //
  151. // command goroutine
  152. //
  153. func (client *Client) recomputeMaxlens() (int, int) {
  154. maxlenTags := 512
  155. maxlenRest := 512
  156. if client.capabilities.Has(caps.MessageTags) {
  157. maxlenTags = 4096
  158. }
  159. if client.capabilities.Has(caps.MaxLine) {
  160. limits := client.server.getLimits()
  161. if limits.LineLen.Tags > maxlenTags {
  162. maxlenTags = limits.LineLen.Tags
  163. }
  164. maxlenRest = limits.LineLen.Rest
  165. }
  166. atomic.StoreUint32(&client.maxlenTags, uint32(maxlenTags))
  167. atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest))
  168. return maxlenTags, maxlenRest
  169. }
  170. // allow these negotiated length limits to be read without locks; this is a convenience
  171. // so that Client.Send doesn't have to acquire any Client locks
  172. func (client *Client) maxlens() (int, int) {
  173. return int(atomic.LoadUint32(&client.maxlenTags)), int(atomic.LoadUint32(&client.maxlenRest))
  174. }
  175. func (client *Client) run() {
  176. var err error
  177. var isExiting bool
  178. var line string
  179. var msg ircmsg.IrcMessage
  180. defer func() {
  181. if r := recover(); r != nil {
  182. client.server.logger.Error("internal",
  183. fmt.Sprintf("Client caused panic: %v\n%s", r, debug.Stack()))
  184. if client.server.RecoverFromErrors() {
  185. client.server.logger.Error("internal", "Disconnecting client and attempting to recover")
  186. } else {
  187. panic(r)
  188. }
  189. }
  190. // ensure client connection gets closed
  191. client.destroy()
  192. }()
  193. client.idletimer = NewIdleTimer(client)
  194. client.idletimer.Start()
  195. // Set the hostname for this client
  196. // (may be overridden by a later PROXY command from stunnel)
  197. client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr())
  198. for {
  199. maxlenTags, maxlenRest := client.recomputeMaxlens()
  200. line, err = client.socket.Read()
  201. if err != nil {
  202. client.Quit("connection closed")
  203. break
  204. }
  205. client.server.logger.Debug("userinput ", client.nick, "<- ", line)
  206. msg, err = ircmsg.ParseLineMaxLen(line, maxlenTags, maxlenRest)
  207. if err == ircmsg.ErrorLineIsEmpty {
  208. continue
  209. } else if err != nil {
  210. client.Quit("received malformed line")
  211. break
  212. }
  213. cmd, exists := Commands[msg.Command]
  214. if !exists {
  215. if len(msg.Command) > 0 {
  216. client.Send(nil, client.server.name, ERR_UNKNOWNCOMMAND, client.nick, msg.Command, "Unknown command")
  217. } else {
  218. client.Send(nil, client.server.name, ERR_UNKNOWNCOMMAND, client.nick, "lastcmd", "No command given")
  219. }
  220. continue
  221. }
  222. isExiting = cmd.Run(client.server, client, msg)
  223. if isExiting || client.isQuitting {
  224. break
  225. }
  226. }
  227. }
  228. //
  229. // idle, quit, timers and timeouts
  230. //
  231. // Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
  232. func (client *Client) Active() {
  233. client.atime = time.Now()
  234. }
  235. // Touch marks the client as alive (as it it has a connection to us and we
  236. // can receive messages from it).
  237. func (client *Client) Touch() {
  238. client.idletimer.Touch()
  239. }
  240. // Ping sends the client a PING message.
  241. func (client *Client) Ping() {
  242. client.Send(nil, "", "PING", client.nick)
  243. }
  244. //
  245. // server goroutine
  246. //
  247. // Register sets the client details as appropriate when entering the network.
  248. func (client *Client) Register() {
  249. client.stateMutex.Lock()
  250. alreadyRegistered := client.registered
  251. client.registered = true
  252. client.stateMutex.Unlock()
  253. if alreadyRegistered {
  254. return
  255. }
  256. client.Touch()
  257. client.updateNickMask("")
  258. client.server.monitorManager.AlertAbout(client, true)
  259. }
  260. // IdleTime returns how long this client's been idle.
  261. func (client *Client) IdleTime() time.Duration {
  262. return time.Since(client.atime)
  263. }
  264. // SignonTime returns this client's signon time as a unix timestamp.
  265. func (client *Client) SignonTime() int64 {
  266. return client.ctime.Unix()
  267. }
  268. // IdleSeconds returns the number of seconds this client's been idle.
  269. func (client *Client) IdleSeconds() uint64 {
  270. return uint64(client.IdleTime().Seconds())
  271. }
  272. // HasNick returns true if the client's nickname is set (used in registration).
  273. func (client *Client) HasNick() bool {
  274. return client.nick != "" && client.nick != "*"
  275. }
  276. // HasUsername returns true if the client's username is set (used in registration).
  277. func (client *Client) HasUsername() bool {
  278. return client.username != "" && client.username != "*"
  279. }
  280. // HasRoleCapabs returns true if client has the given (role) capabilities.
  281. func (client *Client) HasRoleCapabs(capabs ...string) bool {
  282. if client.class == nil {
  283. return false
  284. }
  285. for _, capab := range capabs {
  286. if !client.class.Capabilities[capab] {
  287. return false
  288. }
  289. }
  290. return true
  291. }
  292. // ModeString returns the mode string for this client.
  293. func (client *Client) ModeString() (str string) {
  294. str = "+"
  295. for flag := range client.flags {
  296. str += flag.String()
  297. }
  298. return
  299. }
  300. // Friends refers to clients that share a channel with this client.
  301. func (client *Client) Friends(capabs ...caps.Capability) ClientSet {
  302. friends := make(ClientSet)
  303. // make sure that I have the right caps
  304. hasCaps := true
  305. for _, capab := range capabs {
  306. if !client.capabilities.Has(capab) {
  307. hasCaps = false
  308. break
  309. }
  310. }
  311. if hasCaps {
  312. friends.Add(client)
  313. }
  314. for _, channel := range client.Channels() {
  315. for _, member := range channel.Members() {
  316. // make sure they have all the required caps
  317. hasCaps = true
  318. for _, capab := range capabs {
  319. if !member.capabilities.Has(capab) {
  320. hasCaps = false
  321. break
  322. }
  323. }
  324. if hasCaps {
  325. friends.Add(member)
  326. }
  327. }
  328. }
  329. return friends
  330. }
  331. // updateNick updates `nick` and `nickCasefolded`.
  332. func (client *Client) updateNick(nick string) {
  333. casefoldedName, err := CasefoldName(nick)
  334. if err != nil {
  335. log.Println(fmt.Sprintf("ERROR: Nick [%s] couldn't be casefolded... this should never happen. Printing stacktrace.", client.nick))
  336. debug.PrintStack()
  337. }
  338. client.stateMutex.Lock()
  339. client.nick = nick
  340. client.nickCasefolded = casefoldedName
  341. client.stateMutex.Unlock()
  342. }
  343. // updateNickMask updates the casefolded nickname and nickmask.
  344. func (client *Client) updateNickMask(nick string) {
  345. // on "", just regenerate the nickmask etc.
  346. // otherwise, update the actual nick
  347. if nick != "" {
  348. client.updateNick(nick)
  349. }
  350. client.stateMutex.Lock()
  351. if len(client.vhost) > 0 {
  352. client.hostname = client.vhost
  353. } else {
  354. client.hostname = client.rawHostname
  355. }
  356. nickMaskString := fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.hostname)
  357. nickMaskCasefolded, err := Casefold(nickMaskString)
  358. if err != nil {
  359. log.Println(fmt.Sprintf("ERROR: Nickmask [%s] couldn't be casefolded... this should never happen. Printing stacktrace.", client.nickMaskString))
  360. debug.PrintStack()
  361. }
  362. client.nickMaskString = nickMaskString
  363. client.nickMaskCasefolded = nickMaskCasefolded
  364. client.stateMutex.Unlock()
  365. }
  366. // AllNickmasks returns all the possible nickmasks for the client.
  367. func (client *Client) AllNickmasks() []string {
  368. var masks []string
  369. var mask string
  370. var err error
  371. if len(client.vhost) > 0 {
  372. mask, err = Casefold(fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.vhost))
  373. if err == nil {
  374. masks = append(masks, mask)
  375. }
  376. }
  377. mask, err = Casefold(fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.rawHostname))
  378. if err == nil {
  379. masks = append(masks, mask)
  380. }
  381. mask2, err := Casefold(fmt.Sprintf("%s!%s@%s", client.nick, client.username, utils.IPString(client.socket.conn.RemoteAddr())))
  382. if err == nil && mask2 != mask {
  383. masks = append(masks, mask2)
  384. }
  385. return masks
  386. }
  387. // SetNickname sets the very first nickname for the client.
  388. func (client *Client) SetNickname(nickname string) error {
  389. if client.HasNick() {
  390. client.server.logger.Error("nick", fmt.Sprintf("%s nickname already set, something is wrong with server consistency", client.nickMaskString))
  391. return ErrNickAlreadySet
  392. }
  393. err := client.server.clients.Add(client, nickname)
  394. if err == nil {
  395. client.updateNick(nickname)
  396. }
  397. return err
  398. }
  399. // ChangeNickname changes the existing nickname of the client.
  400. func (client *Client) ChangeNickname(nickname string) error {
  401. origNickMask := client.nickMaskString
  402. err := client.server.clients.Replace(client.nick, nickname, client)
  403. if err == nil {
  404. client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s", client.nick, nickname))
  405. client.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), client.nick, nickname))
  406. client.server.whoWas.Append(client)
  407. client.updateNickMask(nickname)
  408. for friend := range client.Friends() {
  409. friend.Send(nil, origNickMask, "NICK", nickname)
  410. }
  411. }
  412. return err
  413. }
  414. // LoggedIntoAccount returns true if this client is logged into an account.
  415. func (client *Client) LoggedIntoAccount() bool {
  416. return client.account != nil && client.account != &NoAccount
  417. }
  418. // RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
  419. func (client *Client) RplISupport() {
  420. for _, tokenline := range client.server.getISupport().CachedReply {
  421. // ugly trickery ahead
  422. client.Send(nil, client.server.name, RPL_ISUPPORT, append([]string{client.nick}, tokenline...)...)
  423. }
  424. }
  425. // Quit sets the given quit message for the client and tells the client to quit out.
  426. func (client *Client) Quit(message string) {
  427. client.stateMutex.Lock()
  428. alreadyQuit := client.isQuitting
  429. if !alreadyQuit {
  430. client.isQuitting = true
  431. client.quitMessage = message
  432. }
  433. client.stateMutex.Unlock()
  434. if alreadyQuit {
  435. return
  436. }
  437. quitMsg := ircmsg.MakeMessage(nil, client.nickMaskString, "QUIT", message)
  438. quitLine, _ := quitMsg.Line()
  439. errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
  440. errorLine, _ := errorMsg.Line()
  441. client.socket.SetFinalData(quitLine + errorLine)
  442. }
  443. // destroy gets rid of a client, removes them from server lists etc.
  444. func (client *Client) destroy() {
  445. // allow destroy() to execute at most once
  446. client.stateMutex.Lock()
  447. isDestroyed := client.isDestroyed
  448. client.isDestroyed = true
  449. client.stateMutex.Unlock()
  450. if isDestroyed {
  451. return
  452. }
  453. client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", client.nick))
  454. // send quit/error message to client if they haven't been sent already
  455. client.Quit("Connection closed")
  456. client.server.whoWas.Append(client)
  457. friends := client.Friends()
  458. friends.Remove(client)
  459. // remove from connection limits
  460. ipaddr := client.IP()
  461. // this check shouldn't be required but eh
  462. if ipaddr != nil {
  463. client.server.connectionLimiter.RemoveClient(ipaddr)
  464. }
  465. // alert monitors
  466. client.server.monitorManager.AlertAbout(client, false)
  467. // clean up monitor state
  468. client.server.monitorManager.RemoveAll(client)
  469. // clean up channels
  470. client.server.channelJoinPartMutex.Lock()
  471. for channel := range client.channels {
  472. channel.Quit(client)
  473. for _, member := range channel.Members() {
  474. friends.Add(member)
  475. }
  476. }
  477. client.server.channelJoinPartMutex.Unlock()
  478. // clean up server
  479. client.server.clients.Remove(client)
  480. // clean up self
  481. if client.idletimer != nil {
  482. client.idletimer.Stop()
  483. }
  484. client.socket.Close()
  485. // send quit messages to friends
  486. for friend := range friends {
  487. if client.quitMessage == "" {
  488. client.quitMessage = "Exited"
  489. }
  490. friend.Send(nil, client.nickMaskString, "QUIT", client.quitMessage)
  491. }
  492. if !client.exitedSnomaskSent {
  493. client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), client.nick))
  494. }
  495. }
  496. // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
  497. // Adds account-tag to the line as well.
  498. func (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command, target string, message SplitMessage) {
  499. if client.capabilities.Has(caps.MaxLine) {
  500. client.SendFromClient(msgid, from, tags, command, target, message.ForMaxLine)
  501. } else {
  502. for _, str := range message.For512 {
  503. client.SendFromClient(msgid, from, tags, command, target, str)
  504. }
  505. }
  506. }
  507. // SendFromClient sends an IRC line coming from a specific client.
  508. // Adds account-tag to the line as well.
  509. func (client *Client) SendFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) error {
  510. // attach account-tag
  511. if client.capabilities.Has(caps.AccountTag) && from.account != &NoAccount {
  512. if tags == nil {
  513. tags = ircmsg.MakeTags("account", from.account.Name)
  514. } else {
  515. (*tags)["account"] = ircmsg.MakeTagValue(from.account.Name)
  516. }
  517. }
  518. // attach message-id
  519. if len(msgid) > 0 && client.capabilities.Has(caps.MessageTags) {
  520. if tags == nil {
  521. tags = ircmsg.MakeTags("draft/msgid", msgid)
  522. } else {
  523. (*tags)["draft/msgid"] = ircmsg.MakeTagValue(msgid)
  524. }
  525. }
  526. return client.Send(tags, from.nickMaskString, command, params...)
  527. }
  528. var (
  529. // these are all the output commands that MUST have their last param be a trailing.
  530. // this is needed because silly clients like to treat trailing as separate from the
  531. // other params in messages.
  532. commandsThatMustUseTrailing = map[string]bool{
  533. "PRIVMSG": true,
  534. "NOTICE": true,
  535. RPL_WHOISCHANNELS: true,
  536. RPL_USERHOST: true,
  537. }
  538. )
  539. // Send sends an IRC line to the client.
  540. func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error {
  541. // attach server-time
  542. if client.capabilities.Has(caps.ServerTime) {
  543. t := time.Now().UTC().Format("2006-01-02T15:04:05.999Z")
  544. if tags == nil {
  545. tags = ircmsg.MakeTags("time", t)
  546. } else {
  547. (*tags)["time"] = ircmsg.MakeTagValue(t)
  548. }
  549. }
  550. // force trailing, if message requires it
  551. var usedTrailingHack bool
  552. if commandsThatMustUseTrailing[strings.ToUpper(command)] && len(params) > 0 {
  553. lastParam := params[len(params)-1]
  554. // to force trailing, we ensure the final param contains a space
  555. if !strings.Contains(lastParam, " ") {
  556. params[len(params)-1] = lastParam + " "
  557. usedTrailingHack = true
  558. }
  559. }
  560. // send out the message
  561. message := ircmsg.MakeMessage(tags, prefix, command, params...)
  562. maxlenTags, maxlenRest := client.maxlens()
  563. line, err := message.LineMaxLen(maxlenTags, maxlenRest)
  564. if err != nil {
  565. // try not to fail quietly - especially useful when running tests, as a note to dig deeper
  566. // log.Println("Error assembling message:")
  567. // spew.Dump(message)
  568. // debug.PrintStack()
  569. message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
  570. line, _ := message.Line()
  571. client.socket.Write(line)
  572. return err
  573. }
  574. // is we used the trailing hack, we need to strip the final space we appended earlier
  575. if usedTrailingHack {
  576. line = line[:len(line)-3] + "\r\n"
  577. }
  578. client.server.logger.Debug("useroutput", client.nick, " ->", strings.TrimRight(line, "\r\n"))
  579. client.socket.Write(line)
  580. return nil
  581. }
  582. // Notice sends the client a notice from the server.
  583. func (client *Client) Notice(text string) {
  584. limit := 400
  585. if client.capabilities.Has(caps.MaxLine) {
  586. limit = client.server.getLimits().LineLen.Rest - 110
  587. }
  588. lines := wordWrap(text, limit)
  589. for _, line := range lines {
  590. client.Send(nil, client.server.name, "NOTICE", client.nick, line)
  591. }
  592. }
  593. func (client *Client) addChannel(channel *Channel) {
  594. client.stateMutex.Lock()
  595. client.channels[channel] = true
  596. client.stateMutex.Unlock()
  597. }
  598. func (client *Client) removeChannel(channel *Channel) {
  599. client.stateMutex.Lock()
  600. delete(client.channels, channel)
  601. client.stateMutex.Unlock()
  602. }