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.

659 lines
14 KiB

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