diff --git a/examples/config.login b/examples/config.login new file mode 100644 index 00000000..c72dcb27 --- /dev/null +++ b/examples/config.login @@ -0,0 +1,8 @@ +thread = 8 +logger = nil +harbor = 0 +start = "main" +bootstrap = "snlua bootstrap" -- The service for bootstrap +luaservice = "./service/?.lua;./examples/login/?.lua" +lualoader = "lualib/loader.lua" +cpath = "./cservice/?.so" diff --git a/examples/login/client.lua b/examples/login/client.lua new file mode 100644 index 00000000..36ee3e2a --- /dev/null +++ b/examples/login/client.lua @@ -0,0 +1,59 @@ +package.cpath = "luaclib/?.so" + +local socket = require "clientsocket" +local cjson = require "cjson" +local crypt = require "crypt" + +local last +local fd = assert(socket.connect("127.0.0.1", 8001)) +local input = {} + +local function readline() + local line = table.remove(input, 1) + if line then + return line + end + + while true do + local status + status, last = socket.readline(fd, last, input) + if status == nil then + error "Server closed" + end + if not status then + socket.usleep(100) + else + local line = table.remove(input, 1) + if line then + return line + end + end + end +end + +local challenge = crypt.base64decode(readline()) + +local clientkey = crypt.randomkey() +socket.writeline(fd, crypt.base64encode(crypt.dhexchange(clientkey))) +local secret = crypt.dhsecret(crypt.base64decode(readline()), clientkey) + +print("sceret is ", crypt.hexencode(secret)) + +local hmac = crypt.hmac64(challenge, secret) +socket.writeline(fd, crypt.base64encode(hmac)) + +local token = { + user = "hello", + pass = "password", +} + +local etoken = crypt.desencode(secret, cjson.encode(token)) +local b = crypt.base64encode(etoken) +socket.writeline(fd, crypt.base64encode(etoken)) + +print(readline()) + + + + + diff --git a/examples/login/logind.lua b/examples/login/logind.lua new file mode 100644 index 00000000..3c8a3835 --- /dev/null +++ b/examples/login/logind.lua @@ -0,0 +1,25 @@ +local login = require "loginserver" +local json = require "cjson" +local crypt = require "crypt" + +local server = { + host = "127.0.0.1", + port = 8001, +} + +function server.auth_handler(token) + token = json.decode(token) + assert(token.user) + assert(token.pass == "password") + return "sample", token.user +end + +function server.login_handler(server, uid, secret) + print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) +end + +function server.logout_handler(server, uid) + print(string.format("%s@%s is logout", uid, server)) +end + +login(server) diff --git a/examples/login/main.lua b/examples/login/main.lua new file mode 100644 index 00000000..493b2af4 --- /dev/null +++ b/examples/login/main.lua @@ -0,0 +1,5 @@ +local skynet = require "skynet" + +skynet.start(function() + skynet.newservice "logind" +end) diff --git a/lualib/loginserver.lua b/lualib/loginserver.lua new file mode 100644 index 00000000..658ae1cd --- /dev/null +++ b/lualib/loginserver.lua @@ -0,0 +1,133 @@ +local skynet = require "skynet" +local socket = require "socket" +local crypt = require "crypt" +local datacenter = require "datacenter" + +local function launch_slave(auth_handler) + local cmd = {} + + -- set socket buffer limit (8K) + -- If the attacker send large package, close the socket + socket.limit(8192) + + function cmd.auth(fd, addr) + skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) + socket.start(fd) + local challenge = crypt.randomkey() + socket.write(fd, crypt.base64encode(challenge).."\n") + + local handshake = assert(socket.readline(fd), "socket closed") + local clientkey = crypt.base64decode(handshake) + if #clientkey ~= 8 then + error "Invalid client key" + end + local serverkey = crypt.randomkey() + socket.write(fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n") + + local secret = crypt.dhsecret(clientkey, serverkey) + + local response = assert(socket.readline(fd), "socket closed") + local hmac = crypt.hmac64(challenge, secret) + + if hmac ~= crypt.base64decode(response) then + socket.write(fd, "400 Bad Request\n") + error "challenge failed" + end + + local etoken = assert(socket.readline(fd), "socket closed") + + local token = crypt.desdecode(secret, crypt.base64decode(etoken)) + + local ok, server, uid = pcall(auth_handler,token) + + socket.abandon(fd) + return ok, server, uid, secret + end + + skynet.dispatch("lua", function(_,_,command,...) + local f = assert(cmd[command]) + skynet.ret(skynet.pack(f(...))) + end) +end + +local function accept(conf, s, fd, addr) + local ok, server, uid, secret = skynet.call(s, "lua", "auth", fd, addr) + if not ok then + socket.write(fd, "401 Unauthorized\n") + error(server) + end + + socket.start(fd) + + local ok, err = pcall(conf.login_handler, server, uid, secret) + if ok then + socket.write(fd, "200 OK\n") + else + socket.write(fd, "406 Not Acceptable\n") + error(err) + end +end + +local function launch_master(conf) + local instance = conf.instance or 8 + assert(instance > 0) + local host = conf.host or "0.0.0.0" + local port = assert(tonumber(conf.port)) + local slave = {} + local balance = 1 + local cmd = {} + + function cmd.logout(server, uid) + conf.logout_handler(server, uid) + end + + if conf.init then + conf.init() + conf.init = nil + end + + for i=1,instance do + slave[i] = skynet.newservice(SERVICE_NAME) + end + + skynet.dispatch("lua", function(_,_,command, ...) + local f = assert(cmd[command]) + skynet.ret(skynet.pack(f(...))) + end) + + local id = socket.listen(host, port) + skynet.error(string.format("login server listen at : %s %d", host, port)) + socket.start(id , function(fd, addr) + local s = slave[balance] + balance = balance + 1 + if balance > instance then + balance = 1 + end + local ok, err = pcall(accept, conf, s, fd, addr) + if not ok then + skynet.error(string.format("invalid client (fd = %d) error = %s", fd, err)) + end + socket.close(fd) + end) +end + +local function login (conf) + skynet.start(function() + local loginmaster = datacenter.get "login_master" + if loginmaster then + local auth_handler = assert(conf.auth_handler) + launch_master = nil + conf = nil + launch_slave(auth_handler) + else + launch_slave = nil + conf.auth_handler = nil + assert(conf.login_handler) + assert(conf.logout_handler) + datacenter.set("login_master", skynet.self()) + launch_master(conf) + end + end) +end + +return login