Guest User

Untitled

a guest
Jun 18th, 2018
119
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.15 KB | None | 0 0
  1. // My small, family IRC server
  2. //
  3. // This file won't compile by itself because it's only one file from
  4. // my larger family server (movie hosting, Asterisk dialplan, Git
  5. // hosting, personal assistant, etc).
  6. //
  7. // Users authenticate via NICK and PASS. The USER is interpreted as a
  8. // "device" name. That allows each user to connect from multiple
  9. // devices simultaneously while still appearing as only one nick in
  10. // channels. Each nick-device combo has a queue of messages which
  11. // have not yet been delivered. This allows users to disconnect and
  12. // receive their messages when they reconnect.
  13. //
  14. // IRC doesn't have a built in ACK mechanism, so we fake it with PING
  15. // messages. After each PRIVMSG, we send a PING which identifies the
  16. // previous PRIVMSG. Since TCP guarantees in-order arrival, if we
  17. // receive the PONG, we know the client recieved the message too.
  18. // It's a hack, but it allows messages to be queued when users are
  19. // offline.
  20. //
  21. // This file doesn't include the code for our SMS gateway or our
  22. // family IRC bot.
  23. //
  24. // Copyright 2018 Michael Hendricks
  25. //
  26. // Permission is granted to do anything with this work for any purpose
  27. // with or without fee, and with or without attribution.
  28.  
  29. package main
  30.  
  31. import (
  32. "bytes"
  33. "errors"
  34. "fmt"
  35. "io"
  36. "log"
  37. "net"
  38. "runtime/debug"
  39. "strconv"
  40. "strings"
  41. "sync"
  42. "time"
  43.  
  44. "github.com/mndrix/rand"
  45. )
  46.  
  47. // configuration goes here
  48. const serverHostname = `example.org`
  49. const ircTimeout = 5 * time.Minute
  50.  
  51. var ircAuth = map[string]string{
  52. "alice": "secret password",
  53. "bob": "another password",
  54. "charles": "more secrets",
  55. "david": "running out of secrets",
  56. }
  57.  
  58. var routeMap = map[string]func(*client, Message) error{
  59. "JOIN": commandJoin,
  60. "PASS": commandPass,
  61. "PING": commandPing,
  62. "PONG": commandPong,
  63. "PRIVMSG": commandPrivmsg,
  64. "WHOIS": commandWhois,
  65. }
  66.  
  67. type IrcServer struct {
  68. sync.Mutex
  69. clients Clients // all connected clients
  70. }
  71.  
  72. type NetConn struct {
  73. conn net.Conn
  74. buf []byte
  75. }
  76.  
  77. type IrcConn interface {
  78. Addr() string // connection's address (IP:port, etc)
  79. Close() error // close connection (noop if already closed)
  80. Send(Message) error // transmit IRC message to connection
  81. Recv() (Message, bool) // read next IRC message from connection
  82. }
  83.  
  84. type client struct {
  85. conn IrcConn
  86.  
  87. active time.Time // last time client was active
  88. device string // name of the device ("phone", "laptop", etc)
  89. latency time.Duration // latency of most recent ping; -1 if unreachable
  90. name string // user's real name
  91. nick string // user's authenticated nickname
  92. queue []Message // messages sent but not yet acknowledged
  93. sentPing time.Time // time of the most recent, outgoing ping
  94. tags map[string]bool // tags set on this client
  95. }
  96.  
  97. type Clients map[*client]bool
  98.  
  99. type Message struct {
  100. command string
  101. from *client
  102. id string
  103. line string
  104. params []string
  105. time time.Time
  106.  
  107. omitPrefix bool
  108. }
  109.  
  110. var ircServer = &IrcServer{clients: map[*client]bool{}}
  111.  
  112. func ircListen() {
  113. listener, err := net.Listen("tcp", ":6667")
  114. if err != nil {
  115. panic(err)
  116. }
  117. defer listener.Close()
  118. log.Printf("IRC listening on %s", listener.Addr())
  119.  
  120. go ircBot()
  121. for {
  122. conn, err := listener.Accept()
  123. if err != nil {
  124. panic(err)
  125. }
  126. go newClient(&NetConn{conn: conn}).handle()
  127. }
  128. }
  129.  
  130. func (srv *IrcServer) Clients() Clients {
  131. srv.Lock()
  132. defer srv.Unlock()
  133. return srv.clients.Keep(func(*client) bool { return true })
  134. }
  135.  
  136. func (srv *IrcServer) addClient(cl *client) {
  137. srv.Lock()
  138. defer srv.Unlock()
  139.  
  140. // does this user-device combo already have connections?
  141. cl.queue = nil
  142. after := cl.conn.Addr()
  143. remove := make([]*client, 0, len(srv.clients))
  144. for c := range srv.clients {
  145. if c.nick == cl.nick && c.device == cl.device {
  146. before := "(closed)"
  147. if c.conn != nil {
  148. before = c.conn.Addr()
  149. }
  150. log.Printf("Replacing client %s with %s", before, after)
  151. cl.queue = append(cl.queue, c.queue...)
  152. for tag := range c.tags {
  153. cl.setTag(tag)
  154. }
  155. srv.closeClient(c)
  156. remove = append(remove, c)
  157. }
  158. }
  159. for _, c := range remove {
  160. delete(srv.clients, c)
  161. }
  162.  
  163. srv.clients[cl] = true
  164. }
  165.  
  166. func (srv *IrcServer) closeClient(cl *client) {
  167. if err := recover(); err != nil {
  168. log.Printf("PANIC: %s, %s", err, debug.Stack())
  169. }
  170.  
  171. if cl.conn != nil {
  172. err := cl.conn.Close()
  173. if err == nil {
  174. log.Printf("Server closed connection")
  175. } else {
  176. log.Printf("Error closing connection: %s", err)
  177. }
  178. cl.conn = nil
  179. }
  180. }
  181.  
  182. func (conn *NetConn) Addr() string { return conn.conn.RemoteAddr().String() }
  183. func (conn *NetConn) Close() error { return conn.conn.Close() }
  184.  
  185. func (conn *NetConn) Send(msg Message) error {
  186. if conn.conn == nil {
  187. return nil
  188. }
  189. _, err := conn.conn.Write([]byte(msg.String()))
  190. return err
  191. }
  192.  
  193. func newClient(conn IrcConn) *client {
  194. return &client{
  195. conn: conn,
  196. tags: make(map[string]bool),
  197. }
  198. }
  199.  
  200. func (cl *client) next() (msg Message, ok bool) {
  201. msg, ok = cl.conn.Recv()
  202. if ok {
  203. if msg.command != "x-timeout" {
  204. cl.active = time.Now()
  205. log.Printf("%s -> %q", cl.logNick(), msg.line)
  206. }
  207. if time.Since(cl.sentPing) >= ircTimeout {
  208. cl.ping(serverHostname)
  209. }
  210. }
  211. return
  212. }
  213.  
  214. func (cl *NetConn) Recv() (msg Message, ok bool) {
  215. // do we already have a full message in the buffer?
  216. n := 0
  217. if msg, n = ParseMessage(cl.buf); n > 0 {
  218. cl.buf = cl.buf[n:]
  219. ok = true
  220. return
  221. }
  222.  
  223. // nope. read more data into the buffer
  224. cl.conn.SetReadDeadline(time.Now().Add(ircTimeout))
  225. data := make([]byte, 1024)
  226. n, err := cl.conn.Read(data)
  227. if n == 0 && err == nil {
  228. err = errors.New("no data available to read")
  229. }
  230. if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
  231. return Message{command: "x-timeout"}, true
  232. }
  233. switch err {
  234. case nil:
  235. cl.buf = append(cl.buf, data[0:n]...)
  236. return cl.Recv()
  237. case io.EOF:
  238. log.Printf("%s -> Client disconnected", cl.Addr())
  239. default:
  240. log.Printf("%s -> next(): %s (%#v)", cl.Addr(), err, err)
  241. }
  242. return
  243. }
  244.  
  245. func (cl *client) resend(tag string) error {
  246. // resend messages the client missed while away
  247. seen := make(map[string]bool, len(cl.queue))
  248. for _, msg := range cl.queue {
  249. if !seen[msg.id] && msg.command == "PRIVMSG" && tag == msg.params[0] {
  250. seen[msg.id] = true
  251. err := cl.Send(msg)
  252. if err == nil {
  253. err = cl.ping(msg.id)
  254. }
  255. if err != nil {
  256. return err
  257. }
  258. }
  259. }
  260.  
  261. return nil
  262. }
  263.  
  264. func (cl *client) send(cmd string, args ...string) error {
  265. return cl.Send(NewMessage(cmd, args...))
  266. }
  267.  
  268. // Send transmits a single message to the client.
  269. func (cl *client) Send(msg Message) error {
  270. switch msg.command {
  271. case "PING", "PRIVMSG":
  272. cl.queue = append(cl.queue, msg)
  273. }
  274.  
  275. log.Printf("%s <- %q", cl.logNick(), msg.String())
  276. if cl.conn == nil {
  277. return nil
  278. }
  279. return cl.conn.Send(msg)
  280. }
  281.  
  282. func (cl *client) ping(id string) error {
  283. cl.sentPing = time.Now()
  284. return cl.send("PING", id)
  285. }
  286.  
  287. func (cl *client) logNick() string {
  288. if cl.nick == "" {
  289. return "?"
  290. }
  291. return cl.nick
  292. }
  293.  
  294. func (cl *client) hasTag(tag string) bool {
  295. _, ok := cl.tags[tag]
  296. return ok
  297. }
  298.  
  299. func (cl *client) setTag(tag string) {
  300. cl.tags[tag] = true
  301. }
  302.  
  303. func (cl *client) handle() {
  304. defer ircServer.closeClient(cl)
  305. log.Printf("Client connected from %s", cl.conn.Addr())
  306.  
  307. cl.sentPing = time.Now() // pretend that we sent a ping
  308. cl.send("NOTICE", "*", "Hi")
  309. for {
  310. msg, ok := cl.next()
  311. if !ok {
  312. return
  313. }
  314.  
  315. if fn, ok := routeMap[msg.command]; ok {
  316. if err := fn(cl, msg); err != nil {
  317. log.Printf("callback error: %s", err)
  318. return
  319. }
  320. }
  321. }
  322. }
  323.  
  324. func registrationDone(cl *client) error {
  325. // announce that registration is done
  326. sends := []func() error{
  327. func() error { return cl.send("001", cl.nick, "Welcome") },
  328. func() error {
  329. return cl.send(
  330. "004", cl.nick,
  331. serverHostname,
  332. "0.0.1", // version
  333. "ov", // user modes
  334. "bklm", // channel modes
  335. )
  336. },
  337. func() error {
  338. return cl.send(
  339. "005", cl.nick,
  340. "CHANTYPES=#",
  341. "CHANMODES=b,k,l,m",
  342. "PREFIX=(ov)@+",
  343. "are supported",
  344. )
  345. },
  346. func() error { return cl.send("422", cl.nick, "No message today") },
  347. func() error { return cl.resend(cl.nick) },
  348. func() error { return cl.ping(serverHostname) },
  349. }
  350. for _, send := range sends {
  351. err := send()
  352. if err != nil {
  353. return err
  354. }
  355. }
  356.  
  357. return nil
  358. }
  359.  
  360. func commandPass(cl *client, msg Message) error {
  361. password := msg.params[0]
  362. username := ""
  363. device := ""
  364. name := ""
  365. for username == "" || device == "" {
  366. msg, ok := cl.next()
  367. if !ok {
  368. return errors.New("commandPass: trouble reading")
  369. }
  370.  
  371. if msg.command == "NICK" {
  372. username = msg.params[0]
  373. } else if msg.command == "USER" {
  374. device = msg.params[0]
  375. name = msg.params[3]
  376. }
  377. }
  378.  
  379. // verify credentials
  380. correctPassword, ok := ircAuth[username]
  381. if ok && password == correctPassword {
  382. cl.nick = username
  383. cl.setTag(username)
  384. cl.device = device
  385. cl.name = name
  386. ircServer.addClient(cl)
  387. return registrationDone(cl)
  388. }
  389. return cl.send("464", "*", "Wrong username or password")
  390. }
  391.  
  392. func commandPing(cl *client, msg Message) error {
  393. return cl.send("PONG", serverHostname, msg.params[0])
  394. }
  395.  
  396. func commandPong(cl *client, msg Message) error {
  397. // keep messages that have not yet been acknowledged
  398. keep := make([]Message, 0, len(cl.queue))
  399. id := msg.params[0]
  400. for _, msg := range cl.queue {
  401. if msg.id == id {
  402. // message acknowledged. don't keep it
  403. } else if msg.command == "PING" && msg.params[0] == id {
  404. cl.latency = time.Since(msg.time)
  405. // ping acknowledged. don't keep it
  406. } else {
  407. keep = append(keep, msg)
  408. }
  409. }
  410. log.Printf(" acknowledged %d messages", len(cl.queue)-len(keep))
  411. cl.queue = keep
  412.  
  413. log.Printf(" latency %s", cl.latency)
  414. return nil
  415. }
  416.  
  417. func commandJoin(cl *client, req Message) error {
  418. channelName := req.params[0]
  419. if strings.Contains(channelName, ",") {
  420. for _, name := range strings.Split(channelName, ",") {
  421. redo := req
  422. redo.params[0] = name
  423. err := commandJoin(cl, redo)
  424. if err != nil {
  425. return err
  426. }
  427. }
  428. return nil
  429. }
  430. if !strings.HasPrefix(channelName, "#") {
  431. return errors.New("TODO send invalid group error to client")
  432. }
  433.  
  434. // notify channel about the new member
  435. msg := NewMessage("JOIN", channelName).From(cl)
  436. members := ircServer.Clients().Tagged(channelName)
  437. if len(members.Tagged(cl.nick)) == 0 {
  438. // nick not yet in channel
  439. members.Send(msg)
  440. }
  441.  
  442. // this client now belongs to this channel
  443. cl.setTag(channelName)
  444. cl.Send(msg)
  445.  
  446. // provide channel details to the new participant
  447. nicks := members.UniqueNicks(cl.nick)
  448. cl.send("332", cl.nick, channelName, "Discuss "+channelName)
  449. cl.send("353", cl.nick, "=", channelName, strings.Join(nicks, " "))
  450. cl.send("366", cl.nick, channelName, "End of /NAMES list")
  451. cl.resend(channelName)
  452. return nil
  453. }
  454.  
  455. func commandPrivmsg(cl *client, req Message) error {
  456. target := req.params[0]
  457. text := req.params[1]
  458. if text == "" {
  459. return cl.send("412", cl.nick, "No text to send")
  460. }
  461.  
  462. // who gets a copy of the message?
  463. recipients := ircServer.Clients().Tagged(target)
  464. if len(recipients) == 0 {
  465. if phone, ok := ParsePhone(target); ok && cl.nick == "michael" {
  466. go SmsSend(req.From(cl), phone, text)
  467. return nil
  468. } else {
  469. return cl.send("401", cl.nick, target, "No such nick/channel")
  470. }
  471. }
  472. recipients = recipients.Except(cl)
  473.  
  474. // send the actual message
  475. res := NewMessage("PRIVMSG", target, text).From(cl)
  476. err := recipients.Send(res)
  477. if err != nil {
  478. return err
  479. }
  480.  
  481. // conclude with a ping to help guess whether the PRIVMSG got through
  482. for c := range recipients {
  483. go c.ping(res.id)
  484. }
  485. return nil
  486. }
  487.  
  488. func commandWhois(cl *client, msg Message) error {
  489. nick := msg.params[0]
  490. found := ircServer.Clients().Tagged(nick).First()
  491. if found == nil {
  492. cl.send("401", cl.nick, nick, "No such nick/channel")
  493. return nil
  494. }
  495.  
  496. // calculate useful details about the user
  497. idle := fmt.Sprintf("%.0f", time.Since(found.active).Seconds()+1)
  498. latency := found.latency.Round(10 * time.Millisecond).String()
  499. if found.latency.Seconds() > 1 {
  500. latency = found.latency.Round(100 * time.Millisecond).String()
  501. }
  502. if found.latency < 0 {
  503. latency = "unreachable"
  504. }
  505. channels := make([]string, 0, len(found.tags))
  506. for tag := range found.tags {
  507. if strings.HasPrefix(tag, "#") {
  508. channels = append(channels, tag)
  509. }
  510. }
  511.  
  512. // send response
  513. cl.send("311", cl.nick, nick, found.device, serverHostname, "*", found.name)
  514. cl.send("312", cl.nick, nick, serverHostname, "ping "+latency)
  515. cl.send("317", cl.nick, nick, idle, "seconds idle")
  516. cl.send("319", cl.nick, nick, strings.Join(channels, " "))
  517. cl.send("318", cl.nick, nick, "End of /WHOIS list")
  518.  
  519. // update latency info for target of WHOIS
  520. if target := ircServer.Clients().Tagged(nick).First(); target != nil {
  521. target.ping(serverHostname)
  522. }
  523. return nil
  524. }
  525.  
  526. func (cls Clients) Keep(f func(*client) bool) Clients {
  527. out := make(Clients, len(cls))
  528. for cl := range cls {
  529. if f(cl) {
  530. out[cl] = true
  531. }
  532. }
  533. return out
  534. }
  535.  
  536. func (cls Clients) Except(drop *client) Clients {
  537. return cls.Keep(func(cl *client) bool { return cl != drop })
  538. }
  539.  
  540. func (cls Clients) First() *client {
  541. for cl := range cls {
  542. return cl
  543. }
  544. return nil
  545. }
  546.  
  547. func (cls Clients) Tagged(tag string) Clients {
  548. return cls.Keep(func(cl *client) bool { return cl.hasTag(tag) })
  549. }
  550.  
  551. func (recipients Clients) Send(msg Message) (err error) {
  552. // start all deliveries
  553. errCh := make(chan error)
  554. for recipient := range recipients {
  555. go func(r *client) { errCh <- r.Send(msg) }(recipient)
  556. }
  557.  
  558. // wait for them all to conclude
  559. outstanding := len(recipients)
  560. for outstanding > 0 {
  561. if e := <-errCh; e != nil && err == nil {
  562. err = e
  563. }
  564. outstanding--
  565. }
  566. return
  567. }
  568.  
  569. func (cls Clients) UniqueNicks(nicks ...string) []string {
  570. seen := make(map[string]bool, len(cls)+len(nicks))
  571. for _, nick := range nicks {
  572. seen[nick] = true
  573. }
  574. for cl := range cls {
  575. if !seen[cl.nick] {
  576. nicks = append(nicks, cl.nick)
  577. seen[cl.nick] = true
  578. }
  579. }
  580. return nicks
  581. }
  582.  
  583. func NewMessage(command string, params ...string) Message {
  584. return Message{
  585. id: strconv.FormatInt(rand.Int63(), 36),
  586. command: command,
  587. params: params,
  588. time: time.Now(),
  589. }
  590. }
  591.  
  592. // returns a message and the number of bytes consumed, or 0 if a full
  593. // line is not present
  594. func ParseMessage(data []byte) (msg Message, i int) {
  595. // extract the first complete line
  596. i = bytes.Index(data, []byte("\n"))
  597. if i < 0 {
  598. i = 0
  599. return
  600. }
  601. data = bytes.TrimRight(data[0:i], "\r")
  602. msg.line = string(data)
  603. i++
  604.  
  605. // extract the command and other parameters
  606. words := bytes.Split(data, []byte{' '})
  607. for n, word := range words {
  608. if msg.command == "" {
  609. msg.command = string(word)
  610. continue
  611. }
  612. if len(word) == 0 { // bad client
  613. continue
  614. }
  615. if word[0] == ':' { // reassemble final param
  616. word = bytes.Join(words[n:], []byte{' '})
  617. msg.params = append(msg.params, string(word[1:]))
  618. break
  619. }
  620. msg.params = append(msg.params, string(word))
  621. }
  622.  
  623. return
  624. }
  625.  
  626. func (msg Message) String() string {
  627. parts := make([]string, 0, 2+len(msg.params))
  628. if msg.command == "PING" || msg.omitPrefix {
  629. // prefix must be absent
  630. } else if cl := msg.from; cl == nil {
  631. parts = append(parts, ":"+serverHostname)
  632. } else {
  633. prefix := fmt.Sprintf(":%s!%s@%s", cl.nick, cl.device, serverHostname)
  634. parts = append(parts, prefix)
  635. }
  636.  
  637. parts = append(parts, msg.command)
  638. if len(msg.params) > 0 {
  639. parts = append(parts, msg.params...)
  640. parts[len(parts)-1] = ":" + parts[len(parts)-1]
  641. }
  642. return strings.Join(parts, " ") + "\r\n"
  643. }
  644.  
  645. func (msg Message) From(cl *client) Message {
  646. msg.from = cl
  647. return msg
  648. }
Add Comment
Please, Sign In to add comment