Mirror of github.com/gissleh/irc
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.

702 lines
15 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. package irc
  2. import (
  3. "bufio"
  4. "context"
  5. "crypto/rand"
  6. "crypto/tls"
  7. "encoding/binary"
  8. "encoding/hex"
  9. "errors"
  10. "fmt"
  11. mathRand "math/rand"
  12. "net"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "time"
  17. "git.aiterp.net/gisle/irc/ircutil"
  18. "git.aiterp.net/gisle/irc/isupport"
  19. )
  20. var supportedCaps = []string{
  21. "server-time",
  22. "cap-notify",
  23. "multi-prefix",
  24. "userhost-in-names",
  25. }
  26. // ErrNoConnection is returned if
  27. var ErrNoConnection = errors.New("irc: no connection")
  28. // A Client is an IRC client. You need to use New to construct it
  29. type Client struct {
  30. id string
  31. config Config
  32. mutex sync.RWMutex
  33. conn net.Conn
  34. ctx context.Context
  35. cancel context.CancelFunc
  36. events chan *Event
  37. sends chan string
  38. lastSend time.Time
  39. capEnabled map[string]bool
  40. capData map[string]string
  41. capsRequested []string
  42. nick string
  43. user string
  44. host string
  45. quit bool
  46. isupport isupport.ISupport
  47. values map[string]interface{}
  48. }
  49. // New creates a new client. The context can be context.Background if you want manually to
  50. // tear down clients upon quitting.
  51. func New(ctx context.Context, config Config) *Client {
  52. client := &Client{
  53. id: generateClientID(),
  54. values: make(map[string]interface{}),
  55. events: make(chan *Event, 64),
  56. sends: make(chan string, 64),
  57. capEnabled: make(map[string]bool),
  58. capData: make(map[string]string),
  59. config: config.WithDefaults(),
  60. }
  61. client.ctx, client.cancel = context.WithCancel(ctx)
  62. go client.handleEventLoop()
  63. go client.handleSendLoop()
  64. return client
  65. }
  66. // Context gets the client's context. It's cancelled if the parent context used
  67. // in New is, or Destroy is called.
  68. func (client *Client) Context() context.Context {
  69. return client.ctx
  70. }
  71. // ID gets the unique identifier for the client, which could be used in data structures
  72. func (client *Client) ID() string {
  73. client.mutex.RLock()
  74. defer client.mutex.RUnlock()
  75. return client.id
  76. }
  77. // Nick gets the nick of the client
  78. func (client *Client) Nick() string {
  79. client.mutex.RLock()
  80. defer client.mutex.RUnlock()
  81. return client.nick
  82. }
  83. // User gets the user/ident of the client
  84. func (client *Client) User() string {
  85. client.mutex.RLock()
  86. defer client.mutex.RUnlock()
  87. return client.user
  88. }
  89. // Host gets the hostname of the client
  90. func (client *Client) Host() string {
  91. client.mutex.RLock()
  92. defer client.mutex.RUnlock()
  93. return client.host
  94. }
  95. // ISupport gets the client's ISupport. This is mutable, and changes to it
  96. // *will* affect the client.
  97. func (client *Client) ISupport() *isupport.ISupport {
  98. return &client.isupport
  99. }
  100. // Connect connects to the server by addr.
  101. func (client *Client) Connect(addr string, ssl bool) (err error) {
  102. var conn net.Conn
  103. if client.Connected() {
  104. client.Disconnect()
  105. }
  106. client.isupport.Reset()
  107. client.mutex.Lock()
  108. client.quit = false
  109. client.mutex.Unlock()
  110. client.EmitSync(context.Background(), NewEvent("client", "connecting"))
  111. if ssl {
  112. conn, err = tls.Dial("tcp", addr, &tls.Config{
  113. InsecureSkipVerify: client.config.SkipSSLVerification,
  114. })
  115. if err != nil {
  116. return err
  117. }
  118. } else {
  119. conn, err = net.Dial("tcp", addr)
  120. if err != nil {
  121. return err
  122. }
  123. }
  124. client.Emit(NewEvent("client", "connect"))
  125. go func() {
  126. reader := bufio.NewReader(conn)
  127. replacer := strings.NewReplacer("\r", "", "\n", "")
  128. for {
  129. line, err := reader.ReadString('\n')
  130. if err != nil {
  131. break
  132. }
  133. line = replacer.Replace(line)
  134. event, err := ParsePacket(line)
  135. if err != nil {
  136. continue
  137. }
  138. client.Emit(event)
  139. }
  140. client.mutex.Lock()
  141. client.conn = nil
  142. client.mutex.Unlock()
  143. client.Emit(NewEvent("client", "disconnect"))
  144. }()
  145. client.mutex.Lock()
  146. client.conn = conn
  147. client.mutex.Unlock()
  148. return nil
  149. }
  150. // Disconnect disconnects from the server. It will either return the
  151. // close error, or ErrNoConnection if there is no connection
  152. func (client *Client) Disconnect() error {
  153. client.mutex.Lock()
  154. defer client.mutex.Unlock()
  155. if client.conn == nil {
  156. return ErrNoConnection
  157. }
  158. client.quit = true
  159. err := client.conn.Close()
  160. return err
  161. }
  162. // Connected returns true if the client has a connection
  163. func (client *Client) Connected() bool {
  164. client.mutex.RLock()
  165. defer client.mutex.RUnlock()
  166. return client.conn != nil
  167. }
  168. // Send sends a line to the server. A line-feed will be automatically added if one
  169. // is not provided.
  170. func (client *Client) Send(line string) error {
  171. client.mutex.RLock()
  172. conn := client.conn
  173. client.mutex.RUnlock()
  174. if conn == nil {
  175. return ErrNoConnection
  176. }
  177. if !strings.HasSuffix(line, "\n") {
  178. line += "\r\n"
  179. }
  180. _, err := conn.Write([]byte(line))
  181. if err != nil {
  182. client.EmitNonBlocking(NewErrorEvent("network", err.Error()))
  183. client.Disconnect()
  184. }
  185. return err
  186. }
  187. // Sendf is Send with a fmt.Sprintf
  188. func (client *Client) Sendf(format string, a ...interface{}) error {
  189. return client.Send(fmt.Sprintf(format, a...))
  190. }
  191. // SendQueued appends a message to a queue that will only send 2 messages
  192. // per second to avoid flooding. If the queue is ull, a goroutine will be
  193. // spawned to queue it, so this function will always return immediately.
  194. // Order may not be guaranteed, however, but if you're sending 64 messages
  195. // at once that may not be your greatest concern.
  196. //
  197. // Failed sends will be discarded quietly to avoid a backup from being
  198. // thrown on a new connection.
  199. func (client *Client) SendQueued(line string) {
  200. select {
  201. case client.sends <- line:
  202. default:
  203. go func() { client.sends <- line }()
  204. }
  205. }
  206. // SendQueuedf is SendQueued with a fmt.Sprintf
  207. func (client *Client) SendQueuedf(format string, a ...interface{}) {
  208. client.SendQueued(fmt.Sprintf(format, a...))
  209. }
  210. // Emit sends an event through the client's event, and it will return immediately
  211. // unless the internal channel is filled up. The returned context can be used to
  212. // wait for the event, or the client's destruction.
  213. func (client *Client) Emit(event Event) context.Context {
  214. event.ctx, event.cancel = context.WithCancel(client.ctx)
  215. client.events <- &event
  216. return event.ctx
  217. }
  218. // EmitNonBlocking is just like emit, but it will spin off a goroutine if the channel is full.
  219. // This lets it be called from other handlers without ever blocking. See Emit for what the
  220. // returned context is for.
  221. func (client *Client) EmitNonBlocking(event Event) context.Context {
  222. event.ctx, event.cancel = context.WithCancel(client.ctx)
  223. select {
  224. case client.events <- &event:
  225. default:
  226. go func() { client.events <- &event }()
  227. }
  228. return event.ctx
  229. }
  230. // EmitSync emits an event and waits for either its context to complete or the one
  231. // passed to it (e.g. a request's context). It's a shorthand for Emit with its
  232. // return value used in a `select` along with a passed context.
  233. func (client *Client) EmitSync(ctx context.Context, event Event) (err error) {
  234. eventCtx := client.Emit(event)
  235. select {
  236. case <-eventCtx.Done():
  237. {
  238. if err := eventCtx.Err(); err != context.Canceled {
  239. return err
  240. }
  241. return nil
  242. }
  243. case <-ctx.Done():
  244. {
  245. return ctx.Err()
  246. }
  247. }
  248. }
  249. // Value gets a client value.
  250. func (client *Client) Value(key string) (v interface{}, ok bool) {
  251. client.mutex.RLock()
  252. v, ok = client.values[key]
  253. client.mutex.RUnlock()
  254. return
  255. }
  256. // SetValue sets a client value.
  257. func (client *Client) SetValue(key string, value interface{}) {
  258. client.mutex.Lock()
  259. client.values[key] = value
  260. client.mutex.Unlock()
  261. }
  262. // Destroy destroys the client, which will lead to a disconnect. Cancelling the
  263. // parent context will do the same.
  264. func (client *Client) Destroy() {
  265. client.Disconnect()
  266. client.cancel()
  267. close(client.sends)
  268. close(client.events)
  269. }
  270. // Destroyed returns true if the client has been destroyed, either by
  271. // Destroy or the parent context.
  272. func (client *Client) Destroyed() bool {
  273. select {
  274. case <-client.ctx.Done():
  275. return true
  276. default:
  277. return false
  278. }
  279. }
  280. // PrivmsgOverhead returns the overhead on a privmsg to the target. If `action` is true,
  281. // it will also count the extra overhead of a CTCP ACTION.
  282. func (client *Client) PrivmsgOverhead(targetName string, action bool) int {
  283. client.mutex.RLock()
  284. defer client.mutex.RUnlock()
  285. // Return a really safe estimate if user or host is missing.
  286. if client.user == "" || client.host == "" {
  287. return 200
  288. }
  289. return ircutil.MessageOverhead(client.nick, client.user, client.host, targetName, action)
  290. }
  291. // Join joins one or more channels without a key.
  292. func (client *Client) Join(channels ...string) error {
  293. return client.Sendf("JOIN %s", strings.Join(channels, ","))
  294. }
  295. func (client *Client) handleEventLoop() {
  296. ticker := time.NewTicker(time.Second * 30)
  297. for {
  298. select {
  299. case event, ok := <-client.events:
  300. {
  301. if !ok {
  302. goto end
  303. }
  304. client.handleEvent(event)
  305. emit(event, client)
  306. event.cancel()
  307. }
  308. case <-ticker.C:
  309. {
  310. event := NewEvent("client", "tick")
  311. event.ctx, event.cancel = context.WithCancel(client.ctx)
  312. client.handleEvent(&event)
  313. emit(&event, client)
  314. event.cancel()
  315. }
  316. case <-client.ctx.Done():
  317. {
  318. goto end
  319. }
  320. }
  321. }
  322. end:
  323. ticker.Stop()
  324. client.Disconnect()
  325. event := NewEvent("client", "destroy")
  326. event.ctx, event.cancel = context.WithCancel(client.ctx)
  327. client.handleEvent(&event)
  328. emit(&event, client)
  329. event.cancel()
  330. }
  331. func (client *Client) handleSendLoop() {
  332. lastRefresh := time.Time{}
  333. queue := 2
  334. for line := range client.sends {
  335. now := time.Now()
  336. deltaTime := now.Sub(lastRefresh)
  337. if deltaTime < time.Second {
  338. queue--
  339. if queue <= 0 {
  340. time.Sleep(time.Second - deltaTime)
  341. lastRefresh = now
  342. queue = 0
  343. }
  344. } else {
  345. lastRefresh = now
  346. }
  347. client.Send(line)
  348. }
  349. }
  350. // handleEvent is always first and gets to break a few rules.
  351. func (client *Client) handleEvent(event *Event) {
  352. // IRCv3 `server-time`
  353. if timeTag, ok := event.Tags["time"]; ok {
  354. serverTime, err := time.Parse(time.RFC3339Nano, timeTag)
  355. if err == nil && serverTime.Year() > 2000 {
  356. event.Time = serverTime
  357. }
  358. }
  359. switch event.name {
  360. // Ping Pong
  361. case "hook.tick":
  362. {
  363. client.mutex.RLock()
  364. lastSend := time.Since(client.lastSend)
  365. client.mutex.RUnlock()
  366. if lastSend > time.Second*120 {
  367. client.Sendf("PING :%x%x%x", mathRand.Int63(), mathRand.Int63(), mathRand.Int63())
  368. }
  369. }
  370. case "packet.ping":
  371. {
  372. message := "PONG"
  373. for _, arg := range event.Args {
  374. message += " " + arg
  375. }
  376. if event.Text != "" {
  377. message += " :" + event.Text
  378. }
  379. client.Send(message + "")
  380. }
  381. // Client Registration
  382. case "client.connect":
  383. {
  384. client.Send("CAP LS 302")
  385. if client.config.Password != "" {
  386. client.Sendf("PASS :%s", client.config.Password)
  387. }
  388. nick := client.config.Nick
  389. client.mutex.RLock()
  390. if client.nick != "" {
  391. nick = client.nick
  392. }
  393. client.mutex.RUnlock()
  394. client.Sendf("NICK %s", nick)
  395. client.Sendf("USER %s 8 * :%s", client.config.User, client.config.RealName)
  396. }
  397. case "packet.001":
  398. {
  399. client.mutex.Lock()
  400. client.nick = event.Args[0]
  401. client.mutex.Unlock()
  402. client.Sendf("WHO %s", event.Args[0])
  403. }
  404. case "packet.443":
  405. {
  406. client.mutex.RLock()
  407. hasRegistered := client.nick != ""
  408. client.mutex.RUnlock()
  409. if !hasRegistered {
  410. nick := event.Args[1]
  411. // "AltN" -> "AltN+1", ...
  412. prev := client.config.Nick
  413. sent := false
  414. for _, alt := range client.config.Alternatives {
  415. if nick == prev {
  416. client.Sendf("NICK %s", alt)
  417. sent = true
  418. break
  419. }
  420. prev = alt
  421. }
  422. if !sent {
  423. // "LastAlt" -> "Nick23962"
  424. client.Sendf("NICK %s%05d", client.config.Nick, mathRand.Int31n(99999))
  425. }
  426. }
  427. }
  428. // Handle ISupport
  429. case "packet.005":
  430. {
  431. for _, token := range event.Args[1:] {
  432. kvpair := strings.Split(token, "=")
  433. if len(kvpair) == 2 {
  434. client.isupport.Set(kvpair[0], kvpair[1])
  435. } else {
  436. client.isupport.Set(kvpair[0], "")
  437. }
  438. }
  439. }
  440. // Capability negotiation
  441. case "packet.cap":
  442. {
  443. capCommand := event.Args[1]
  444. capTokens := strings.Split(event.Text, " ")
  445. switch capCommand {
  446. case "LS":
  447. {
  448. for _, token := range capTokens {
  449. split := strings.SplitN(token, "=", 2)
  450. key := split[0]
  451. if len(key) == 0 {
  452. continue
  453. }
  454. if len(split) == 2 {
  455. client.capData[key] = split[1]
  456. }
  457. for i := range supportedCaps {
  458. if supportedCaps[i] == token {
  459. client.mutex.Lock()
  460. client.capsRequested = append(client.capsRequested, token)
  461. client.mutex.Unlock()
  462. break
  463. }
  464. }
  465. }
  466. if len(event.Args) < 3 || event.Args[2] != "*" {
  467. client.mutex.RLock()
  468. requestedCount := len(client.capsRequested)
  469. client.mutex.RUnlock()
  470. if requestedCount > 0 {
  471. client.mutex.RLock()
  472. requestedCaps := strings.Join(client.capsRequested, " ")
  473. client.mutex.RUnlock()
  474. client.Send("CAP REQ :" + requestedCaps)
  475. } else {
  476. client.Send("CAP END")
  477. }
  478. }
  479. }
  480. case "ACK":
  481. {
  482. for _, token := range capTokens {
  483. client.mutex.Lock()
  484. if client.capEnabled[token] {
  485. client.capEnabled[token] = true
  486. }
  487. client.mutex.Unlock()
  488. }
  489. client.Send("CAP END")
  490. }
  491. case "NAK":
  492. {
  493. // Remove offenders
  494. for _, token := range capTokens {
  495. client.mutex.Lock()
  496. for i := range client.capsRequested {
  497. if token == client.capsRequested[i] {
  498. client.capsRequested = append(client.capsRequested[:i], client.capsRequested[i+1:]...)
  499. break
  500. }
  501. }
  502. client.mutex.Unlock()
  503. }
  504. client.mutex.RLock()
  505. requestedCaps := strings.Join(client.capsRequested, " ")
  506. client.mutex.RUnlock()
  507. client.Send("CAP REQ :" + requestedCaps)
  508. }
  509. case "NEW":
  510. {
  511. requests := make([]string, 0, len(capTokens))
  512. for _, token := range capTokens {
  513. for i := range supportedCaps {
  514. if supportedCaps[i] == token {
  515. requests = append(requests, token)
  516. }
  517. }
  518. }
  519. if len(requests) > 0 {
  520. client.Send("CAP REQ :" + strings.Join(requests, " "))
  521. }
  522. }
  523. case "DEL":
  524. {
  525. for _, token := range capTokens {
  526. client.mutex.Lock()
  527. if client.capEnabled[token] {
  528. client.capEnabled[token] = false
  529. }
  530. client.mutex.Unlock()
  531. }
  532. }
  533. }
  534. }
  535. // User/host detection
  536. case "packet.352": // WHO reply
  537. {
  538. // Example args: test * ~irce 127.0.0.1 localhost.localnetwork Gissleh H :0 ...
  539. nick := event.Args[5]
  540. user := event.Args[2]
  541. host := event.Args[3]
  542. if nick == client.nick {
  543. client.mutex.Lock()
  544. client.user = user
  545. client.host = host
  546. client.mutex.Unlock()
  547. }
  548. }
  549. case "packet.chghost":
  550. {
  551. if event.Nick == client.nick {
  552. client.mutex.Lock()
  553. client.user = event.Args[1]
  554. client.host = event.Args[2]
  555. client.mutex.Unlock()
  556. }
  557. }
  558. }
  559. }
  560. func generateClientID() string {
  561. bytes := make([]byte, 12)
  562. _, err := rand.Read(bytes)
  563. // Ugly fallback if crypto rand doesn't work.
  564. if err != nil {
  565. rng := mathRand.NewSource(time.Now().UnixNano())
  566. result := strconv.FormatInt(rng.Int63(), 16)
  567. for len(result) < 24 {
  568. result += strconv.FormatInt(rng.Int63(), 16)
  569. }
  570. return result[:24]
  571. }
  572. binary.BigEndian.PutUint32(bytes, uint32(time.Now().Unix()))
  573. return hex.EncodeToString(bytes)
  574. }