Software /
code /
prosody
Changeset
528:5fabb5aeed9d
Fix GPL'ing MIT/X licensed code :)
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Wed, 03 Dec 2008 16:44:03 +0000 |
parents | 527:98aaa20af6a2 |
children | 530:22ef9bab0834 |
files | net/server.lua |
diffstat | 1 files changed, 836 insertions(+), 858 deletions(-) [+] |
line wrap: on
line diff
--- a/net/server.lua Wed Dec 03 16:20:58 2008 +0000 +++ b/net/server.lua Wed Dec 03 16:44:03 2008 +0000 @@ -1,860 +1,838 @@ --- Prosody IM v0.1 --- Copyright (C) 2008 Matthew Wild --- Copyright (C) 2008 Waqas Hussain --- --- This program is free software; you can redistribute it and/or --- modify it under the terms of the GNU General Public License --- as published by the Free Software Foundation; either version 2 --- of the License, or (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program; if not, write to the Free Software --- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- +--[[ + + 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 ) -- this function wraps a server + + local dispatch, disconnect = listener.listener, listener.disconnect -- dangerous + + local wrapclient, err + + 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 + wrapclient = wrapsslclient + wrapclient = wraptlsclient + 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 -- end of buffer + + 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 handler._dispatchdata(); 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 + 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 ) + out_put("setting linger on "..tostring(socket)) + socket:setoption("linger", { on = true, timeout = 10 }); + --// private closures of the object //-- + + local writequeue = { } -- buffer for messages to send + + local eol, fatal_send_error -- end of buffer + + 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 ---[[ - - server.lua by blastbeat of the luadch project - - re-used here under the MIT/X Consortium License - - - this script contains the server loop of the program - - other scripts can reg a server here - -]]-- - -----------------------------------// 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 ) -- this function wraps a server - - local dispatch, disconnect = listener.listener, listener.disconnect -- dangerous - - local wrapclient, err - - 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 - wrapclient = wrapsslclient - wrapclient = wraptlsclient - 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 -- end of buffer - - 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 handler._dispatchdata(); 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 - 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 ) - out_put("setting linger on "..tostring(socket)) - socket:setoption("linger", { on = true, timeout = 10 }); - --// private closures of the object //-- - - local writequeue = { } -- buffer for messages to send - - local eol, fatal_send_error -- end of buffer - - 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 handler._dispatchdata(); 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); - if handler.need_tls then - out_put("server.lua: uh-oh... we still want tls, something must be wrong"); - end - 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) ) - 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( 1 ) - 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.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 - handler.need_tls = nil - socketlist[ client ] = handler - readlen = readlen + 1 - readlist[ readlen ] = client - 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 - - 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 handler.dispatchdata(); 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 - 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 ) -- 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 ) -- 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 - --[[print(readlen, writelen) - for _, s in ipairs(readlist) do print("R:", tostring(s)) end - for _, s in ipairs(writelist) do print("W:", tostring(s)) end - out_put("select()"..os.time())]] - 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, - wraptlsclient = wraptlsclient, -} + 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 handler._dispatchdata(); 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); + if handler.need_tls then + out_put("server.lua: uh-oh... we still want tls, something must be wrong"); + end + 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) ) + 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( 1 ) + 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.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 + handler.need_tls = nil + socketlist[ client ] = handler + readlen = readlen + 1 + readlist[ readlen ] = client + 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 + + 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 handler.dispatchdata(); 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 + 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 ) -- 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 ) -- 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 + --[[print(readlen, writelen) + for _, s in ipairs(readlist) do print("R:", tostring(s)) end + for _, s in ipairs(writelist) do print("W:", tostring(s)) end + out_put("select()"..os.time())]] + 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, + wraptlsclient = wraptlsclient, +}