loginserver.lua 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. local skynet = require "skynet"
  2. require "skynet.manager"
  3. local socket = require "skynet.socket"
  4. local crypt = require "skynet.crypt"
  5. local table = table
  6. local string = string
  7. local assert = assert
  8. --[[
  9. Protocol:
  10. line (\n) based text protocol
  11. 1. Server->Client : base64(8bytes random challenge)
  12. 2. Client->Server : base64(8bytes handshake client key)
  13. 3. Server: Gen a 8bytes handshake server key
  14. 4. Server->Client : base64(DH-Exchange(server key))
  15. 5. Server/Client secret := DH-Secret(client key/server key)
  16. 6. Client->Server : base64(HMAC(challenge, secret))
  17. 7. Client->Server : DES(secret, base64(token))
  18. 8. Server : call auth_handler(token) -> server, uid (A user defined method)
  19. 9. Server : call login_handler(server, uid, secret) ->subid (A user defined method)
  20. 10. Server->Client : 200 base64(subid)
  21. Error Code:
  22. 400 Bad Request . challenge failed
  23. 401 Unauthorized . unauthorized by auth_handler
  24. 403 Forbidden . login_handler failed
  25. 406 Not Acceptable . already in login (disallow multi login)
  26. Success:
  27. 200 base64(subid)
  28. ]]
  29. local socket_error = {}
  30. local function assert_socket(service, v, fd)
  31. if v then
  32. return v
  33. else
  34. skynet.error(string.format("%s failed: socket (fd = %d) closed", service, fd))
  35. error(socket_error)
  36. end
  37. end
  38. local function write(service, fd, text)
  39. assert_socket(service, socket.write(fd, text), fd)
  40. end
  41. local function launch_slave(auth_handler)
  42. local function auth(fd, addr)
  43. -- set socket buffer limit (8K)
  44. -- If the attacker send large package, close the socket
  45. socket.limit(fd, 8192)
  46. local challenge = crypt.randomkey()
  47. write("auth", fd, crypt.base64encode(challenge).."\n")
  48. local handshake = assert_socket("auth", socket.readline(fd), fd)
  49. local clientkey = crypt.base64decode(handshake)
  50. if #clientkey ~= 8 then
  51. error "Invalid client key"
  52. end
  53. local serverkey = crypt.randomkey()
  54. write("auth", fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n")
  55. local secret = crypt.dhsecret(clientkey, serverkey)
  56. local response = assert_socket("auth", socket.readline(fd), fd)
  57. local hmac = crypt.hmac64(challenge, secret)
  58. if hmac ~= crypt.base64decode(response) then
  59. write("auth", fd, "400 Bad Request\n")
  60. error "challenge failed"
  61. end
  62. local etoken = assert_socket("auth", socket.readline(fd),fd)
  63. local token = crypt.desdecode(secret, crypt.base64decode(etoken))
  64. local ok, server, uid = pcall(auth_handler,token)
  65. return ok, server, uid, secret
  66. end
  67. local function ret_pack(ok, err, ...)
  68. if ok then
  69. return skynet.pack(err, ...)
  70. else
  71. if err == socket_error then
  72. return skynet.pack(nil, "socket error")
  73. else
  74. return skynet.pack(false, err)
  75. end
  76. end
  77. end
  78. local function auth_fd(fd, addr)
  79. skynet.error(string.format("connect from %s (fd = %d)", addr, fd))
  80. socket.start(fd) -- may raise error here
  81. local msg, len = ret_pack(pcall(auth, fd, addr))
  82. socket.abandon(fd) -- never raise error here
  83. return msg, len
  84. end
  85. skynet.dispatch("lua", function(_,_,...)
  86. local ok, msg, len = pcall(auth_fd, ...)
  87. if ok then
  88. skynet.ret(msg,len)
  89. else
  90. skynet.ret(skynet.pack(false, msg))
  91. end
  92. end)
  93. end
  94. local user_login = {}
  95. local function accept(conf, s, fd, addr)
  96. -- call slave auth
  97. local ok, server, uid, secret = skynet.call(s, "lua", fd, addr)
  98. -- slave will accept(start) fd, so we can write to fd later
  99. if not ok then
  100. if ok ~= nil then
  101. write("response 401", fd, "401 Unauthorized\n")
  102. end
  103. error(server)
  104. end
  105. if not conf.multilogin then
  106. if user_login[uid] then
  107. write("response 406", fd, "406 Not Acceptable\n")
  108. error(string.format("User %s is already login", uid))
  109. end
  110. user_login[uid] = true
  111. end
  112. local ok, err = pcall(conf.login_handler, server, uid, secret)
  113. -- unlock login
  114. user_login[uid] = nil
  115. if ok then
  116. err = err or ""
  117. write("response 200",fd, "200 "..crypt.base64encode(err).."\n")
  118. else
  119. write("response 403",fd, "403 Forbidden\n")
  120. error(err)
  121. end
  122. end
  123. local function launch_master(conf)
  124. local instance = conf.instance or 8
  125. assert(instance > 0)
  126. local host = conf.host or "0.0.0.0"
  127. local port = assert(tonumber(conf.port))
  128. local slave = {}
  129. local balance = 1
  130. skynet.dispatch("lua", function(_,source,command, ...)
  131. skynet.ret(skynet.pack(conf.command_handler(command, ...)))
  132. end)
  133. for i=1,instance do
  134. table.insert(slave, skynet.newservice(SERVICE_NAME))
  135. end
  136. skynet.error(string.format("login server listen at : %s %d", host, port))
  137. local id = socket.listen(host, port)
  138. socket.start(id , function(fd, addr)
  139. local s = slave[balance]
  140. balance = balance + 1
  141. if balance > #slave then
  142. balance = 1
  143. end
  144. local ok, err = pcall(accept, conf, s, fd, addr)
  145. if not ok then
  146. if err ~= socket_error then
  147. skynet.error(string.format("invalid client (fd = %d) error = %s", fd, err))
  148. end
  149. end
  150. socket.close_fd(fd) -- We haven't call socket.start, so use socket.close_fd rather than socket.close.
  151. end)
  152. end
  153. local function login(conf)
  154. local name = "." .. (conf.name or "login")
  155. skynet.start(function()
  156. local loginmaster = skynet.localname(name)
  157. if loginmaster then
  158. local auth_handler = assert(conf.auth_handler)
  159. launch_master = nil
  160. conf = nil
  161. launch_slave(auth_handler)
  162. else
  163. launch_slave = nil
  164. conf.auth_handler = nil
  165. assert(conf.login_handler)
  166. assert(conf.command_handler)
  167. skynet.register(name)
  168. launch_master(conf)
  169. end
  170. end)
  171. end
  172. return login