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.

703 lines
15 KiB

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