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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2014-2015 Edmund Huber
  3. // Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
  4. // released under the MIT license
  5. package irc
  6. import (
  7. "fmt"
  8. "net"
  9. "time"
  10. )
  11. const (
  12. IDLE_TIMEOUT = time.Minute // how long before a client is considered idle
  13. QUIT_TIMEOUT = time.Minute // how long after idle before a client is kicked
  14. )
  15. type Client struct {
  16. atime time.Time
  17. authorized bool
  18. awayMessage Text
  19. capabilities CapabilitySet
  20. capState CapState
  21. channels ChannelSet
  22. ctime time.Time
  23. flags map[UserMode]bool
  24. hasQuit bool
  25. hops uint
  26. hostname Name
  27. idleTimer *time.Timer
  28. nick Name
  29. quitTimer *time.Timer
  30. realname Text
  31. registered bool
  32. server *Server
  33. socket *Socket
  34. username Name
  35. }
  36. func NewClient(server *Server, conn net.Conn) *Client {
  37. now := time.Now()
  38. socket := NewSocket(conn)
  39. client := &Client{
  40. atime: now,
  41. authorized: server.password == nil,
  42. capState: CapNone,
  43. capabilities: make(CapabilitySet),
  44. channels: make(ChannelSet),
  45. ctime: now,
  46. flags: make(map[UserMode]bool),
  47. server: server,
  48. socket: &socket,
  49. }
  50. client.Touch()
  51. go client.run()
  52. return client
  53. }
  54. //
  55. // command goroutine
  56. //
  57. func (client *Client) run() {
  58. var command Command
  59. var err error
  60. var line string
  61. // Set the hostname for this client. The client may later send a PROXY
  62. // command from stunnel that sets the hostname to something more accurate.
  63. client.hostname = AddrLookupHostname(client.socket.conn.RemoteAddr())
  64. for err == nil {
  65. //TODO(dan): does this read sockets correctly and split lines properly? (think that ZNC bug that kept happening with mammon)
  66. if line, err = client.socket.Read(); err != nil {
  67. command = NewQuitCommand("connection closed")
  68. } else if command, err = ParseCommand(line); err != nil {
  69. switch err {
  70. case ErrParseCommand:
  71. //TODO(dan): why is this a notice? there's a proper numeric for this I swear
  72. client.Reply(RplNotice(client.server, client,
  73. NewText("failed to parse command")))
  74. }
  75. // so the read loop will continue
  76. err = nil
  77. continue
  78. } else if checkPass, ok := command.(checkPasswordCommand); ok {
  79. checkPass.LoadPassword(client.server)
  80. // Block the client thread while handling a potentially expensive
  81. // password bcrypt operation. Since the server is single-threaded
  82. // for commands, we don't want the server to perform the bcrypt,
  83. // blocking anyone else from sending commands until it
  84. // completes. This could be a form of DoS if handled naively.
  85. checkPass.CheckPassword()
  86. }
  87. client.send(command)
  88. }
  89. }
  90. func (client *Client) send(command Command) {
  91. command.SetClient(client)
  92. client.server.commands <- command
  93. }
  94. // quit timer goroutine
  95. func (client *Client) connectionTimeout() {
  96. client.send(NewQuitCommand("connection timeout"))
  97. }
  98. //
  99. // idle timer goroutine
  100. //
  101. func (client *Client) connectionIdle() {
  102. client.server.idle <- client
  103. }
  104. //
  105. // server goroutine
  106. //
  107. func (client *Client) Active() {
  108. client.atime = time.Now()
  109. }
  110. func (client *Client) Touch() {
  111. if client.quitTimer != nil {
  112. client.quitTimer.Stop()
  113. }
  114. if client.idleTimer == nil {
  115. client.idleTimer = time.AfterFunc(IDLE_TIMEOUT, client.connectionIdle)
  116. } else {
  117. client.idleTimer.Reset(IDLE_TIMEOUT)
  118. }
  119. }
  120. func (client *Client) Idle() {
  121. client.Reply(RplPing(client.server))
  122. if client.quitTimer == nil {
  123. client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout)
  124. } else {
  125. client.quitTimer.Reset(QUIT_TIMEOUT)
  126. }
  127. }
  128. func (client *Client) Register() {
  129. if client.registered {
  130. return
  131. }
  132. client.registered = true
  133. client.Touch()
  134. }
  135. func (client *Client) destroy() {
  136. // clean up channels
  137. for channel := range client.channels {
  138. channel.Quit(client)
  139. }
  140. // clean up server
  141. client.server.clients.Remove(client)
  142. // clean up self
  143. if client.idleTimer != nil {
  144. client.idleTimer.Stop()
  145. }
  146. if client.quitTimer != nil {
  147. client.quitTimer.Stop()
  148. }
  149. client.socket.Close()
  150. Log.debug.Printf("%s: destroyed", client)
  151. }
  152. func (client *Client) IdleTime() time.Duration {
  153. return time.Since(client.atime)
  154. }
  155. func (client *Client) SignonTime() int64 {
  156. return client.ctime.Unix()
  157. }
  158. func (client *Client) IdleSeconds() uint64 {
  159. return uint64(client.IdleTime().Seconds())
  160. }
  161. func (client *Client) HasNick() bool {
  162. return client.nick != ""
  163. }
  164. func (client *Client) HasUsername() bool {
  165. return client.username != ""
  166. }
  167. // <mode>
  168. func (c *Client) ModeString() (str string) {
  169. for flag := range c.flags {
  170. str += flag.String()
  171. }
  172. if len(str) > 0 {
  173. str = "+" + str
  174. }
  175. return
  176. }
  177. func (c *Client) UserHost() Name {
  178. username := "*"
  179. if c.HasUsername() {
  180. username = c.username.String()
  181. }
  182. return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname))
  183. }
  184. func (c *Client) Nick() Name {
  185. if c.HasNick() {
  186. return c.nick
  187. }
  188. return Name("*")
  189. }
  190. func (c *Client) Id() Name {
  191. return c.UserHost()
  192. }
  193. func (c *Client) String() string {
  194. return c.Id().String()
  195. }
  196. func (client *Client) Friends() ClientSet {
  197. friends := make(ClientSet)
  198. friends.Add(client)
  199. for channel := range client.channels {
  200. for member := range channel.members {
  201. friends.Add(member)
  202. }
  203. }
  204. return friends
  205. }
  206. func (client *Client) SetNickname(nickname Name) {
  207. if client.HasNick() {
  208. Log.error.Printf("%s nickname already set!", client)
  209. return
  210. }
  211. client.nick = nickname
  212. client.server.clients.Add(client)
  213. }
  214. func (client *Client) ChangeNickname(nickname Name) {
  215. // Make reply before changing nick to capture original source id.
  216. reply := RplNick(client, nickname)
  217. client.server.clients.Remove(client)
  218. client.server.whoWas.Append(client)
  219. client.nick = nickname
  220. client.server.clients.Add(client)
  221. for friend := range client.Friends() {
  222. friend.Reply(reply)
  223. }
  224. }
  225. func (client *Client) Reply(reply string) error {
  226. //TODO(dan): We'll be passing around real message objects instead of raw strings
  227. return client.socket.WriteLine(reply)
  228. }
  229. func (client *Client) Quit(message Text) {
  230. if client.hasQuit {
  231. return
  232. }
  233. client.hasQuit = true
  234. client.Reply(RplError("quit"))
  235. client.server.whoWas.Append(client)
  236. friends := client.Friends()
  237. friends.Remove(client)
  238. client.destroy()
  239. if len(friends) > 0 {
  240. reply := RplQuit(client, message)
  241. for friend := range friends {
  242. friend.Reply(reply)
  243. }
  244. }
  245. }