Software /
code /
prosody
File
net/server.lua @ 658:1952fdcf1017
Fix specifying ports in config, and SSL support
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Sat, 27 Dec 2008 21:20:09 +0000 |
parent | 657:7f1946174d4b |
child | 659:c424bec771d9 |
line wrap: on
line source
--[[ server.lua by blastbeat of the luadch project re-used here under the MIT/X Consortium License Modifications (C) 2008 Matthew Wild, Waqas Hussain ]]-- ----------------------------------// DECLARATION //-- --// constants //-- local STAT_UNIT = 1 / ( 1024 * 1024 ) -- mb --// lua functions //-- local function use( what ) return _G[ what ] end local type = use "type" local pairs = use "pairs" local ipairs = use "ipairs" local tostring = use "tostring" local collectgarbage = use "collectgarbage" --// lua libs //-- local table = use "table" local coroutine = use "coroutine" --// lua lib methods //-- local table_concat = table.concat local table_remove = table.remove local string_sub = use'string'.sub local coroutine_wrap = coroutine.wrap local coroutine_yield = coroutine.yield local print = print; local out_put = function () end --print; local out_error = print; --// extern libs //-- local luasec = select(2, pcall(require, "ssl")) local luasocket = require "socket" --// extern lib methods //-- local ssl_wrap = ( luasec and luasec.wrap ) local socket_bind = luasocket.bind local socket_select = luasocket.select local ssl_newcontext = ( luasec and luasec.newcontext ) --// functions //-- local loop local stats local addtimer local closeall local addserver local firetimer local closesocket local removesocket local wrapserver local wraptcpclient local wrapsslclient --// tables //-- local listener local readlist local writelist local socketlist local timelistener --// simple data types //-- local _ local readlen = 0 -- length of readlist local writelen = 0 -- lenght of writelist local sendstat= 0 local receivestat = 0 ----------------------------------// DEFINITION //-- listener = { } -- key = port, value = table readlist = { } -- array with sockets to read from writelist = { } -- arrary with sockets to write to socketlist = { } -- key = socket, value = wrapped socket timelistener = { } stats = function( ) return receivestat, sendstat end wrapserver = function( listener, socket, ip, serverport, mode, sslctx, wrapper_function ) -- this function wraps a server local dispatch, disconnect = listener.listener, listener.disconnect -- dangerous local wrapclient, err out_put("Starting a new server on "..tostring(serverport).." with ssl: "..tostring(sslctx)); out_put(traceback()) if sslctx then if not ssl_newcontext then return nil, "luasec not found" end if type( sslctx ) ~= "table" then out_error "server.lua: wrong server sslctx" return nil, "wrong server sslctx" end sslctx, err = ssl_newcontext( sslctx ) if not sslctx then err = err or "wrong sslctx parameters" out_error( "server.lua: ", err ) return nil, err end end if wrapper_function then wrapclient = wrapper_function elseif sslctx then wrapclient = wrapsslclient else wrapclient = wraptcpclient end local accept = socket.accept local close = socket.close --// public methods of the object //-- local handler = { } handler.shutdown = function( ) end --[[handler.listener = function( data, err ) return ondata( handler, data, err ) end]] handler.ssl = function( ) return sslctx and true or false end handler.close = function( closed ) _ = not closed and close( socket ) writelen = removesocket( writelist, socket, writelen ) readlen = removesocket( readlist, socket, readlen ) socketlist[ socket ] = nil handler = nil end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.socket = function( ) return socket end handler.receivedata = function( ) local client, err = accept( socket ) -- try to accept if client then local ip, clientport = client:getpeername( ) client:settimeout( 0 ) local handler, client, err = wrapclient( listener, client, ip, serverport, clientport, mode, sslctx ) -- wrap new client socket if err then -- error while wrapping ssl socket return false end out_put( "server.lua: accepted new client connection from ", ip, ":", clientport ) return dispatch( handler ) elseif err then -- maybe timeout or something else out_put( "server.lua: error with new client connection: ", err ) return false end end return handler end wrapsslclient = function( listener, socket, ip, serverport, clientport, mode, sslctx ) -- this function wraps a ssl cleint local dispatch, disconnect = listener.listener, listener.disconnect --// transform socket to ssl object //-- local err socket, err = ssl_wrap( socket, sslctx ) -- wrap socket if err then out_put( "server.lua: ssl error: ", err ) return nil, nil, err -- fatal error end socket:settimeout( 0 ) --// private closures of the object //-- local writequeue = { } -- buffer for messages to send local eol, fatal_send_error, wants_closing local sstat, rstat = 0, 0 --// local import of socket methods //-- local send = socket.send local receive = socket.receive local close = socket.close --local shutdown = socket.shutdown --// public methods of the object //-- local handler = { } handler.getstats = function( ) return rstat, sstat end handler.listener = function( data, err ) return listener( handler, data, err ) end handler.ssl = function( ) return true end handler.send = function( _, data, i, j ) return send( socket, data, i, j ) end handler.receive = function( pattern, prefix ) return receive( socket, pattern, prefix ) end handler.shutdown = function( pattern ) --return shutdown( socket, pattern ) end handler.close = function( closed ) if eol and not fatal_send_error then -- There is data in the buffer, and we haven't experienced -- an error trying to send yet, so we'll flush the buffer now handler._dispatchdata(); if eol then -- and there is *still* data in the buffer -- we'll give up for now, and close later wants_closing = true; return; end end close( socket ) writelen = ( eol and removesocket( writelist, socket, writelen ) ) or writelen readlen = removesocket( readlist, socket, readlen ) socketlist[ socket ] = nil out_put "server.lua: closed handler and removed socket from list" end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.clientport = function( ) return clientport end handler.write = function( data ) if not eol then writelen = writelen + 1 writelist[ writelen ] = socket eol = 0 end eol = eol + 1 writequeue[ eol ] = data end handler.writequeue = function( ) return writequeue end handler.socket = function( ) return socket end handler.mode = function( ) return mode end handler._receivedata = function( ) local data, err, part = receive( socket, mode ) -- receive data in "mode" if not err or ( err == "timeout" or err == "wantread" ) then -- received something local data = data or part or "" local count = #data * STAT_UNIT rstat = rstat + count receivestat = receivestat + count --out_put( "server.lua: read data '", data, "', error: ", err ) return dispatch( handler, data, err ) else -- connections was closed or fatal error out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false end end handler._dispatchdata = function( ) -- this function writes data to handlers local buffer = table_concat( writequeue, "", 1, eol ) local succ, err, byte = send( socket, buffer ) local count = ( succ or 0 ) * STAT_UNIT sstat = sstat + count sendstat = sendstat + count out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport ) if succ then -- sending succesful --writequeue = { } eol = nil writelen = removesocket( writelist, socket, writelen ) -- delete socket from writelist if wants_closing then handler.close(); end return true elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write buffer = string_sub( buffer, byte + 1, -1 ) -- new buffer writequeue[ 1 ] = buffer -- insert new buffer in queue eol = 1 return true else -- connection was closed during sending or fatal error fatal_send_error = true; out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false end end -- // COMPAT // -- handler.getIp = handler.ip handler.getPort = handler.clientport --// handshake //-- local wrote handler.handshake = coroutine_wrap( function( client ) local err for i = 1, 10 do -- 10 handshake attemps _, err = client:dohandshake( ) if not err then out_put( "server.lua: ssl handshake done" ) writelen = ( wrote and removesocket( writelist, socket, writelen ) ) or writelen handler.receivedata = handler._receivedata -- when handshake is done, replace the handshake function with regular functions handler.dispatchdata = handler._dispatchdata return dispatch( handler ) else out_put( "server.lua: error during ssl handshake: ", err ) if err == "wantwrite" then if wrote == nil then writelen = writelen + 1 writelist[ writelen ] = client wrote = true end end coroutine_yield( handler, nil, err ) -- handshake not finished end end _ = err ~= "closed" and close( socket ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false -- handshake failed end ) handler.receivedata = handler.handshake handler.dispatchdata = handler.handshake handler.handshake( socket ) -- do handshake socketlist[ socket ] = handler readlen = readlen + 1 readlist[ readlen ] = socket return handler, socket end wraptlsclient = function( listener, socket, ip, serverport, clientport, mode, sslctx ) -- this function wraps a tls cleint local dispatch, disconnect = listener.listener, listener.disconnect --// transform socket to ssl object //-- local err socket:settimeout( 0 ) --// private closures of the object //-- local writequeue = { } -- buffer for messages to send local eol, fatal_send_error, wants_closing local sstat, rstat = 0, 0 --// local import of socket methods //-- local send = socket.send local receive = socket.receive local close = socket.close --local shutdown = socket.shutdown --// public methods of the object //-- local handler = { } handler.getstats = function( ) return rstat, sstat end handler.listener = function( data, err ) return listener( handler, data, err ) end handler.ssl = function( ) return false end handler.send = function( _, data, i, j ) return send( socket, data, i, j ) end handler.receive = function( pattern, prefix ) return receive( socket, pattern, prefix ) end handler.shutdown = function( pattern ) --return shutdown( socket, pattern ) end handler.close = function( closed ) if eol and not fatal_send_error then -- There is data in the buffer, and we haven't experienced -- an error trying to send yet, so we'll flush the buffer now handler._dispatchdata(); if eol then -- and there is *still* data in the buffer -- we'll give up for now, and close later wants_closing = true; return; end end close( socket ) writelen = ( eol and removesocket( writelist, socket, writelen ) ) or writelen readlen = removesocket( readlist, socket, readlen ) socketlist[ socket ] = nil out_put "server.lua: closed handler and removed socket from list" end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.clientport = function( ) return clientport end handler.write = function( data ) if not eol then writelen = writelen + 1 writelist[ writelen ] = socket eol = 0 end eol = eol + 1 writequeue[ eol ] = data end handler.writequeue = function( ) return writequeue end handler.socket = function( ) return socket end handler.mode = function( ) return mode end handler._receivedata = function( ) local data, err, part = receive( socket, mode ) -- receive data in "mode" if not err or ( err == "timeout" or err == "wantread" ) then -- received something local data = data or part or "" local count = #data * STAT_UNIT rstat = rstat + count receivestat = receivestat + count --out_put( "server.lua: read data '", data, "', error: ", err ) return dispatch( handler, data, err ) else -- connections was closed or fatal error out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false end end handler._dispatchdata = function( ) -- this function writes data to handlers local buffer = table_concat( writequeue, "", 1, eol ) local succ, err, byte = send( socket, buffer ) local count = ( succ or 0 ) * STAT_UNIT sstat = sstat + count sendstat = sendstat + count out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport ) if succ then -- sending succesful --writequeue = { } eol = nil writelen = removesocket( writelist, socket, writelen ) -- delete socket from writelist if handler.need_tls then out_put("server.lua: connection is ready for tls handshake"); handler.starttls(true); end if wants_closing then handler.close(); end return true elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write buffer = string_sub( buffer, byte + 1, -1 ) -- new buffer writequeue[ 1 ] = buffer -- insert new buffer in queue eol = 1 return true else -- connection was closed during sending or fatal error fatal_send_error = true; -- :( out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false end end handler.receivedata, handler.dispatchdata = handler._receivedata, handler._dispatchdata; -- // COMPAT // -- handler.getIp = handler.ip handler.getPort = handler.clientport --// handshake //-- local wrote, read handler.starttls = function (now) if not now then out_put("server.lua: we need to do tls, but delaying until later"); handler.need_tls = true; return; end out_put( "server.lua: attempting to start tls on "..tostring(socket) ) local oldsocket = socket; socket, err = ssl_wrap( socket, sslctx ) -- wrap socket out_put("sslwrapped socket is "..tostring(socket)); if err then out_put( "server.lua: ssl error: ", err ) return nil, nil, err -- fatal error end socket:settimeout(0); -- Add the new socket to our system socketlist[ socket ] = handler readlen = readlen + 1 readlist[ readlen ] = socket -- Remove traces of the old socket readlen = removesocket( readlist, oldsocket, readlen ) socketlist [ oldsocket ] = nil; send = socket.send receive = socket.receive close = socket.close handler.ssl = function( ) return true end handler.send = function( _, data, i, j ) return send( socket, data, i, j ) end handler.receive = function( pattern, prefix ) return receive( socket, pattern, prefix ) end handler.starttls = nil; handler.need_tls = nil handler.handshake = coroutine_wrap( function( client ) local err for i = 1, 10 do -- 10 handshake attemps _, err = client:dohandshake( ) if not err then out_put( "server.lua: ssl handshake done" ) writelen = ( wrote and removesocket( writelist, socket, writelen ) ) or writelen handler.receivedata = handler._receivedata -- when handshake is done, replace the handshake function with regular functions handler.dispatchdata = handler._dispatchdata; return true; else out_put( "server.lua: error during ssl handshake: ", err ) if err == "wantwrite" then if wrote == nil then writelen = writelen + 1 writelist[ writelen ] = client wrote = true end end coroutine_yield( handler, nil, err ) -- handshake not finished end end _ = err ~= "closed" and close( socket ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false -- handshake failed end ) handler.receivedata = handler.handshake handler.dispatchdata = handler.handshake handler.handshake( socket ) -- do handshake end socketlist[ socket ] = handler readlen = readlen + 1 readlist[ readlen ] = socket return handler, socket end wraptcpclient = function( listener, socket, ip, serverport, clientport, mode ) -- this function wraps a socket local dispatch, disconnect = listener.listener, listener.disconnect --// private closures of the object //-- local writequeue = { } -- list for messages to send local eol, fatal_send_error, wants_closing socket:settimeout(0); local rstat, sstat = 0, 0 --// local import of socket methods //-- local send = socket.send local receive = socket.receive local close = socket.close local shutdown = socket.shutdown --// public methods of the object //-- local handler = { } handler.getstats = function( ) return rstat, sstat end handler.listener = function( data, err ) return listener( handler, data, err ) end handler.ssl = function( ) return false end handler.send = function( _, data, i, j ) return send( socket, data, i, j ) end handler.receive = function( pattern, prefix ) return receive( socket, pattern, prefix ) end handler.shutdown = function( pattern ) return shutdown( socket, pattern ) end handler.close = function( closed ) if eol and not fatal_send_error then -- There is data in the buffer, and we haven't experienced -- an error trying to send yet, so we'll flush the buffer now handler.dispatchdata(); if eol then -- and there is *still* data in the buffer -- we'll give up for now, and close later wants_closing = true; return; end end _ = not closed and shutdown( socket ) _ = not closed and close( socket ) writelen = ( eol and removesocket( writelist, socket, writelen ) ) or writelen readlen = removesocket( readlist, socket, readlen ) socketlist[ socket ] = nil out_put "server.lua: closed handler and removed socket from list" end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.clientport = function( ) return clientport end handler.write = function( data ) if not eol then writelen = writelen + 1 writelist[ writelen ] = socket eol = 0 end eol = eol + 1 writequeue[ eol ] = data end handler.writequeue = function( ) return writequeue end handler.socket = function( ) return socket end handler.mode = function( ) return mode end handler.receivedata = function( ) local data, err, part = receive( socket, mode ) -- receive data in "mode" if not err or ( err == "timeout" or err == "wantread" ) then -- received something local data = data or part or "" local count = #data * STAT_UNIT rstat = rstat + count receivestat = receivestat + count --out_put( "server.lua: read data '", data, "', error: ", err ) return dispatch( handler, data, err ) else -- connections was closed or fatal error out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false end end handler.dispatchdata = function( ) -- this function writes data to handlers local buffer = table_concat( writequeue, "", 1, eol ) local succ, err, byte = send( socket, buffer ) local count = ( succ or 0 ) * STAT_UNIT sstat = sstat + count sendstat = sendstat + count out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport ) if succ then -- sending succesful --writequeue = { } eol = nil writelen = removesocket( writelist, socket, writelen ) -- delete socket from writelist if wants_closing then handler.close(); end return true elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write buffer = string_sub( buffer, byte + 1, -1 ) -- new buffer writequeue[ 1 ] = buffer -- insert new buffer in queue eol = 1 return true else -- connection was closed during sending or fatal error fatal_send_error = true; -- :'-( out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) handler.close( ) disconnect( handler, err ) writequeue = nil handler = nil return false end end -- // COMPAT // -- handler.getIp = handler.ip handler.getPort = handler.clientport socketlist[ socket ] = handler readlen = readlen + 1 readlist[ readlen ] = socket return handler, socket end addtimer = function( listener ) timelistener[ #timelistener + 1 ] = listener end firetimer = function( listener ) for i, listener in ipairs( timelistener ) do listener( ) end end addserver = function( listeners, port, addr, mode, sslctx, wrapper_function ) -- this function provides a way for other scripts to reg a server local err if type( listeners ) ~= "table" then err = "invalid listener table" else for name, func in pairs( listeners ) do if type( func ) ~= "function" then --err = "invalid listener function" break end end end if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then err = "invalid port" elseif listener[ port ] then err= "listeners on port '" .. port .. "' already exist" elseif sslctx and not luasec then err = "luasec not found" end if err then out_error( "server.lua: ", err ) return nil, err end addr = addr or "*" local server, err = socket_bind( addr, port ) if err then out_error( "server.lua: ", err ) return nil, err end local handler, err = wrapserver( listeners, server, addr, port, mode, sslctx, wrapper_function ) -- wrap new server socket if not handler then server:close( ) return nil, err end server:settimeout( 0 ) readlen = readlen + 1 readlist[ readlen ] = server listener[ port ] = listeners socketlist[ server ] = handler out_put( "server.lua: new server listener on ", addr, ":", port ) return true end removesocket = function( tbl, socket, len ) -- this function removes sockets from a list for i, target in ipairs( tbl ) do if target == socket then len = len - 1 table_remove( tbl, i ) return len end end return len end closeall = function( ) for sock, handler in pairs( socketlist ) do handler.shutdown( ) handler.close( ) socketlist[ sock ] = nil end writelist, readlist, socketlist = { }, { }, { } end closesocket = function( socket ) writelen = removesocket( writelist, socket, writelen ) readlen = removesocket( readlist, socket, readlen ) socketlist[ socket ] = nil socket:close( ) end loop = function( ) -- this is the main loop of the program --signal_set( "hub", "run" ) repeat local read, write, err = socket_select( readlist, writelist, 1 ) -- 1 sec timeout, nice for timers for i, socket in ipairs( write ) do -- send data waiting in writequeues local handler = socketlist[ socket ] if handler then handler.dispatchdata( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen end end for i, socket in ipairs( read ) do -- receive data local handler = socketlist[ socket ] if handler then handler.receivedata( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen end end firetimer( ) until false return end ----------------------------------// BEGIN //-- ----------------------------------// PUBLIC INTERFACE //-- return { add = addserver, loop = loop, stats = stats, closeall = closeall, addtimer = addtimer, wraptcpclient = wraptcpclient, wrapsslclient = wrapsslclient, wraptlsclient = wraptlsclient, }