79b2f388227875b689e573d52ef026869b98cafa
[prosody.git] / net / server_event.lua
1 --[[\r
2 \r
3 \r
4                         server.lua based on lua/libevent by blastbeat\r
5 \r
6                         notes:\r
7                         -- when using luaevent, never register 2 or more EV_READ at one socket, same for EV_WRITE\r
8                         -- you cant even register a new EV_READ/EV_WRITE callback inside another one\r
9                         -- never call eventcallback:close( ) from inside eventcallback\r
10                         -- to do some of the above, use timeout events or something what will called from outside\r
11                         -- dont let garbagecollect eventcallbacks, as long they are running\r
12                         -- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing\r
13 \r
14 --]]\r
15 \r
16 \r
17 local SCRIPT_NAME           = "server_event.lua"\r
18 local SCRIPT_VERSION        = "0.05"\r
19 local SCRIPT_AUTHOR         = "blastbeat"\r
20 local LAST_MODIFIED         = "2009/11/20"\r
21 \r
22 local cfg = {\r
23         MAX_CONNECTIONS       = 100000,  -- max per server connections (use "ulimit -n" on *nix)\r
24         MAX_HANDSHAKE_ATTEMPS = 10,  -- attemps to finish ssl handshake\r
25         HANDSHAKE_TIMEOUT     = 1,  -- timout in seconds per handshake attemp\r
26         MAX_READ_LENGTH       = 1024 * 1024 * 1024 * 1024,  -- max bytes allowed to read from sockets\r
27         MAX_SEND_LENGTH       = 1024 * 1024 * 1024 * 1024,  -- max bytes size of write buffer (for writing on sockets)\r
28         ACCEPT_DELAY          = 10,  -- seconds to wait until the next attemp of a full server to accept\r
29         READ_TIMEOUT          = 60 * 30,  -- timeout in seconds for read data from socket\r
30         WRITE_TIMEOUT         = 30,  -- timeout in seconds for write data on socket\r
31         CONNECT_TIMEOUT       = 10,  -- timeout in seconds for connection attemps\r
32         CLEAR_DELAY           = 5,  -- seconds to wait for clearing interface list (and calling ondisconnect listeners) \r
33         DEBUG                 = true,  -- show debug messages\r
34 }\r
35 \r
36 local function use(x) return rawget(_G, x); end\r
37 local print = use "print"\r
38 local pcall = use "pcall"\r
39 local ipairs = use "ipairs"\r
40 local string = use "string"\r
41 local select = use "select"\r
42 local require = use "require"\r
43 local tostring = use "tostring"\r
44 local coroutine = use "coroutine"\r
45 local setmetatable = use "setmetatable"\r
46 \r
47 local ssl = use "ssl"\r
48 local socket = use "socket"\r
49 \r
50 local log = require ("util.logger").init("socket")\r
51 \r
52 local function debug(...)\r
53         return log("debug", ("%s "):rep(select('#', ...)), ...)\r
54 end\r
55 local vdebug = debug;\r
56 \r
57 local bitor = ( function( ) -- thx Rici Lake\r
58         local hasbit = function( x, p )\r
59                 return x % ( p + p ) >= p\r
60         end\r
61         return function( x, y ) \r
62                 local p = 1\r
63                 local z = 0\r
64                 local limit = x > y and x or y\r
65                 while p <= limit do \r
66                         if hasbit( x, p ) or hasbit( y, p ) then\r
67                                 z = z + p\r
68                         end\r
69                         p = p + p\r
70                 end\r
71                 return z\r
72         end\r
73 end )( )\r
74 \r
75 local event = require "luaevent.core"\r
76 local base = event.new( )\r
77 local EV_READ = event.EV_READ\r
78 local EV_WRITE = event.EV_WRITE\r
79 local EV_TIMEOUT = event.EV_TIMEOUT\r
80 \r
81 local EV_READWRITE = bitor( EV_READ, EV_WRITE )\r
82 \r
83 local interfacelist = ( function( )  -- holds the interfaces for sockets\r
84         local array = { }\r
85         local len = 0\r
86         return function( method, arg )\r
87                 if "add" == method then\r
88                         len = len + 1\r
89                         array[ len ] = arg\r
90                         arg:_position( len )\r
91                         return len\r
92                 elseif "delete" == method then\r
93                         if len <= 0 then\r
94                                 return nil, "array is already empty"\r
95                         end\r
96                         local position = arg:_position()  -- get position in array\r
97                         if position ~= len then\r
98                                 local interface = array[ len ]  -- get last interface\r
99                                 array[ position ] = interface  -- copy it into free position\r
100                                 array[ len ] = nil  -- free last position\r
101                                 interface:_position( position )  -- set new position in array\r
102                         else  -- free last position\r
103                                 array[ len ] = nil\r
104                         end\r
105                         len = len - 1\r
106                         return len    \r
107                 else\r
108                         return array\r
109                 end\r
110         end\r
111 end )( )\r
112 \r
113 -- Client interface methods\r
114 local interface_mt\r
115 do\r
116         interface_mt = {}; interface_mt.__index = interface_mt;\r
117         \r
118         local addevent = base.addevent\r
119         local coroutine_wrap, coroutine_yield = coroutine.wrap,coroutine.yield\r
120         local string_len = string.len\r
121         \r
122         -- Private methods\r
123         function interface_mt:_position(new_position)\r
124                         self.position = new_position or self.position\r
125                         return self.position;\r
126         end\r
127         function interface_mt:_close() -- regs event to start self:_destroy()\r
128                         local callback = function( )\r
129                                 self:_destroy();\r
130                                 self.eventclose = nil\r
131                                 return -1\r
132                         end\r
133                         self.eventclose = addevent( base, nil, EV_TIMEOUT, callback, 0 )\r
134                         return true\r
135         end\r
136         \r
137         function interface_mt:_start_connection(plainssl) -- should be called from addclient\r
138                         local callback = function( event )\r
139                                 if EV_TIMEOUT == event then  -- timout during connection\r
140                                         self.fatalerror = "connection timeout"\r
141                                         self:ontimeout()  -- call timeout listener\r
142                                         self:_close()\r
143                                         debug( "new connection failed. id:", self.id, "error:", self.fatalerror )\r
144                                 else\r
145                                         if plainssl then  -- start ssl session\r
146                                                 self:_start_ssl( self.listener.onconnect )\r
147                                         else  -- normal connection\r
148                                                 self:_start_session( self.listener.onconnect )\r
149                                         end\r
150                                         debug( "new connection established. id:", self.id )\r
151                                 end\r
152                                 self.eventconnect = nil\r
153                                 return -1\r
154                         end\r
155                         self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )\r
156                         return true\r
157         end\r
158         function interface_mt:_start_session(onconnect) -- new session, for example after startssl\r
159                 if self.type == "client" then\r
160                         local callback = function( )\r
161                                 self:_lock( false,  false, false )\r
162                                 --vdebug( "start listening on client socket with id:", self.id )      \r
163                                 self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT )  -- register callback\r
164                                 self:onconnect()\r
165                                 self.eventsession = nil\r
166                                 return -1\r
167                         end\r
168                         self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 )\r
169                 else\r
170                         self:_lock( false )\r
171                         --vdebug( "start listening on server socket with id:", self.id )\r
172                         self.eventread = addevent( base, self.conn, EV_READ, self.readcallback )  -- register callback\r
173                 end\r
174                 return true\r
175         end\r
176         function interface_mt:_start_ssl(arg) -- old socket will be destroyed, therefore we have to close read/write events first\r
177                         --vdebug( "starting ssl session with client id:", self.id )\r
178                         local _\r
179                         _ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!\r
180                         _ = self.eventwrite and self.eventwrite:close( )\r
181                         self.eventread, self.eventwrite = nil, nil\r
182                         local err\r
183                         self.conn, err = ssl.wrap( self.conn, self._sslctx )\r
184                         if err then\r
185                                 self.fatalerror = err\r
186                                 self.conn = nil  -- cannot be used anymore\r
187                                 if "onconnect" == arg then\r
188                                         self.ondisconnect = nil  -- dont call this when client isnt really connected\r
189                                 end\r
190                                 self:_close()\r
191                                 debug( "fatal error while ssl wrapping:", err )\r
192                                 return false\r
193                         end\r
194                         self.conn:settimeout( 0 )  -- set non blocking\r
195                         local handshakecallback = coroutine_wrap(\r
196                                 function( event )\r
197                                         local _, err\r
198                                         local attempt = 0\r
199                                         local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPS\r
200                                         while attempt < 1000 do  -- no endless loop\r
201                                                 attempt = attempt + 1\r
202                                                 debug( "ssl handshake of client with id:"..tostring(self).."attemp:"..attempt )\r
203                                                 if attempt > maxattempt then\r
204                                                         self.fatalerror = "max handshake attemps exceeded"\r
205                                                 elseif EV_TIMEOUT == event then\r
206                                                         self.fatalerror = "timeout during handshake"\r
207                                                 else\r
208                                                         _, err = self.conn:dohandshake( )\r
209                                                         if not err then\r
210                                                                 self:_lock( false, false, false )  -- unlock the interface; sending, closing etc allowed\r
211                                                                 self.send = self.conn.send  -- caching table lookups with new client object\r
212                                                                 self.receive = self.conn.receive\r
213                                                                 local onsomething\r
214                                                                 if "onconnect" == arg then  -- trigger listener\r
215                                                                         onsomething = self.onconnect\r
216                                                                 else\r
217                                                                         onsomething = self.onsslconnection\r
218                                                                 end\r
219                                                                 self:_start_session( onsomething )\r
220                                                                 debug( "ssl handshake done" )\r
221                                                                 self.eventhandshake = nil\r
222                                                                 return -1\r
223                                                         end\r
224                                                         debug( "error during ssl handshake:", err ) \r
225                                                         if err == "wantwrite" then\r
226                                                                 event = EV_WRITE\r
227                                                         elseif err == "wantread" then\r
228                                                                 event = EV_READ\r
229                                                         else\r
230                                                                 self.fatalerror = err\r
231                                                         end            \r
232                                                 end\r
233                                                 if self.fatalerror then\r
234                                                         if "onconnect" == arg then\r
235                                                                 self.ondisconnect = nil  -- dont call this when client isnt really connected\r
236                                                         end\r
237                                                         self:_close()\r
238                                                         debug( "handshake failed because:", self.fatalerror )\r
239                                                         self.eventhandshake = nil\r
240                                                         return -1\r
241                                                 end\r
242                                                 event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT )  -- yield this monster...\r
243                                         end\r
244                                 end\r
245                         )\r
246                         debug "starting handshake..."\r
247                         self:_lock( false, true, true )  -- unlock read/write events, but keep interface locked \r
248                         self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT )\r
249                         return true\r
250         end\r
251         function interface_mt:_destroy()  -- close this interface + events and call last listener\r
252                         debug( "closing client with id:", self.id )\r
253                         self:_lock( true, true, true )  -- first of all, lock the interface to avoid further actions\r
254                         local _\r
255                         _ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!\r
256                         if self.type == "client" then\r
257                                 _ = self.eventwrite and self.eventwrite:close( )\r
258                                 _ = self.eventhandshake and self.eventhandshake:close( )\r
259                                 _ = self.eventstarthandshake and self.eventstarthandshake:close( )\r
260                                 _ = self.eventconnect and self.eventconnect:close( )\r
261                                 _ = self.eventsession and self.eventsession:close( )\r
262                                 _ = self.eventwritetimeout and self.eventwritetimeout:close( )\r
263                                 _ = self.eventreadtimeout and self.eventreadtimeout:close( )\r
264                                 _ = self.ondisconnect and self:ondisconnect( self.fatalerror )  -- call ondisconnect listener (wont be the case if handshake failed on connect)\r
265                                 _ = self.conn and self.conn:close( ) -- close connection, must also be called outside of any socket registered events!\r
266                                 _ = self._server and self._server:counter(-1);\r
267                                 self.eventread, self.eventwrite = nil, nil\r
268                                 self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil\r
269                                 self.readcallback, self.writecallback = nil, nil\r
270                         else\r
271                                 self.conn:close( )\r
272                                 self.eventread, self.eventclose = nil, nil\r
273                                 self.interface, self.readcallback = nil, nil\r
274                         end\r
275                         interfacelist( "delete", self )\r
276                         return true\r
277         end\r
278         \r
279         function interface_mt:_lock(nointerface, noreading, nowriting)  -- lock or unlock this interface or events\r
280                         self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting\r
281                         return nointerface, noreading, nowriting\r
282         end\r
283 \r
284         function interface_mt:counter(c)\r
285                 if c then\r
286                         self._connections = self._connections + c\r
287                 end\r
288                 return self._connections\r
289         end\r
290         \r
291         -- Public methods\r
292         function interface_mt:write(data)\r
293                 if self.nowriting then return nil, "locked" end\r
294                 --vdebug( "try to send data to client, id/data:", self.id, data )\r
295                 data = tostring( data )\r
296                 local len = string_len( data )\r
297                 local total = len + self.writebufferlen\r
298                 if total > cfg.MAX_SEND_LENGTH then  -- check buffer length\r
299                         local err = "send buffer exceeded"\r
300                         debug( "error:", err )  -- to much, check your app\r
301                         return nil, err\r
302                 end \r
303                 self.writebuffer = self.writebuffer .. data -- new buffer\r
304                 self.writebufferlen = total\r
305                 if not self.eventwrite then  -- register new write event\r
306                         --vdebug( "register new write event" )\r
307                         self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT )\r
308                 end\r
309                 return true\r
310         end\r
311         function interface_mt:close(now)\r
312                 if self.nointerface then return nil, "locked"; end\r
313                 debug( "try to close client connection with id:", self.id )\r
314                 if self.type == "client" then\r
315                         self.fatalerror = "client to close"\r
316                         if ( not self.eventwrite ) or now then  -- try to close immediately\r
317                                 self:_lock( true, true, true )\r
318                                 self:_close()\r
319                                 return true\r
320                         else  -- wait for incomplete write request\r
321                                 self:_lock( true, true, false )\r
322                                 debug "closing delayed until writebuffer is empty"\r
323                                 return nil, "writebuffer not empty, waiting"\r
324                         end\r
325                 else\r
326                         debug( "try to close server with id:", self.id, "args:", now )\r
327                         self.fatalerror = "server to close"\r
328                         self:_lock( true )\r
329                         local count = 0\r
330                         for _, item in ipairs( interfacelist( ) ) do\r
331                                 if ( item.type ~= "server" ) and ( item._server == self ) then  -- client/server match\r
332                                         if item:close( now ) then  -- writebuffer was empty\r
333                                                 count = count + 1\r
334                                         end\r
335                                 end\r
336                         end\r
337                         local timeout = 0  -- dont wait for unfinished writebuffers of clients...\r
338                         if not now then\r
339                                 timeout = cfg.WRITE_TIMEOUT  -- ...or wait for it\r
340                         end\r
341                         self:_close( timeout )  -- add new event to remove the server interface\r
342                         debug( "seconds remained until server is closed:", timeout )\r
343                         return count  -- returns finished clients with empty writebuffer\r
344                 end\r
345         end\r
346         \r
347         function interface_mt:server()\r
348                 return self._server or self;\r
349         end\r
350         \r
351         function interface_mt:port()\r
352                 return self._port\r
353         end\r
354         \r
355         function interface_mt:ip()\r
356                 return self._ip\r
357         end\r
358         \r
359         function interface_mt:ssl()\r
360                 return self._usingssl\r
361         end\r
362 \r
363         function interface_mt:type()\r
364                 return self._type or "client"\r
365         end\r
366         \r
367         function interface_mt:connections()\r
368                 return self._connections\r
369         end\r
370         \r
371         function interface_mt:address()\r
372                 return self.addr\r
373         end\r
374         \r
375         function interface_mt:set_sslctx(sslctx)\r
376                 self._sslctx = sslctx;\r
377                 if sslctx then\r
378                         self.starttls = nil; -- use starttls() of interface_mt\r
379                 else\r
380                         self.starttls = false; -- prevent starttls()\r
381                 end\r
382         end\r
383         end\r
384         \r
385         function interface_mt:starttls()\r
386                 debug( "try to start ssl at client id:", self.id )\r
387                 local err\r
388                 if not self._sslctx then  -- no ssl available\r
389                         err = "no ssl context available"\r
390                 elseif self._usingssl then  -- startssl was already called\r
391                         err = "ssl already active"\r
392                 end\r
393                 if err then\r
394                         debug( "error:", err )\r
395                         return nil, err      \r
396                 end\r
397                 self._usingssl = true\r
398                 self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event\r
399                         self.startsslcallback = nil\r
400                         self:_start_ssl();\r
401                         self.eventstarthandshake = nil\r
402                         return -1\r
403                 end\r
404                 if not self.eventwrite then\r
405                         self:_lock( true, true, true )  -- lock the interface, to not disturb the handshake\r
406                         self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 )  -- add event to start handshake\r
407                 else  -- wait until writebuffer is empty\r
408                         self:_lock( true, true, false )\r
409                         debug "ssl session delayed until writebuffer is empty..."\r
410                 end\r
411                 return true\r
412         end\r
413         \r
414         -- Stub handlers\r
415         function interface_mt:onconnect()\r
416         end\r
417         function interface_mt:onincoming()\r
418         end\r
419         function interface_mt:ondisconnect()\r
420         end\r
421         function interface_mt:ontimeout()\r
422         end\r
423 end                     \r
424 \r
425 -- End of client interface methods\r
426 \r
427 local handleclient;\r
428 do\r
429         local string_sub = string.sub  -- caching table lookups\r
430         local string_len = string.len\r
431         local addevent = base.addevent\r
432         local coroutine_wrap = coroutine.wrap\r
433         local socket_gettime = socket.gettime\r
434         local coroutine_yield = coroutine.yield\r
435         function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface\r
436                 --vdebug("creating client interfacce...")\r
437                 local interface = {\r
438                         type = "client";\r
439                         conn = client;\r
440                         currenttime = socket_gettime( );  -- safe the origin\r
441                         writebuffer = "";  -- writebuffer\r
442                         writebufferlen = 0;  -- length of writebuffer\r
443                         send = client.send;  -- caching table lookups\r
444                         receive = client.receive;\r
445                         onconnect = listener.onconnect;  -- will be called when client disconnects\r
446                         ondisconnect = listener.ondisconnect;  -- will be called when client disconnects\r
447                         onincoming = listener.onincoming;  -- will be called when client sends data\r
448                         ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs\r
449                         eventread = false, eventwrite = false, eventclose = false,\r
450                         eventhandshake = false, eventstarthandshake = false;  -- event handler\r
451                         eventconnect = false, eventsession = false;  -- more event handler...\r
452                         eventwritetimeout = false;  -- even more event handler...\r
453                         eventreadtimeout = false;\r
454                         fatalerror = false;  -- error message\r
455                         writecallback = false;  -- will be called on write events\r
456                         readcallback = false;  -- will be called on read events\r
457                         nointerface = true;  -- lock/unlock parameter of this interface\r
458                         noreading = false, nowriting = false;  -- locks of the read/writecallback\r
459                         startsslcallback = false;  -- starting handshake callback\r
460                         position = false;  -- position of client in interfacelist\r
461                         \r
462                         -- Properties\r
463                         _ip = ip, _port = port, _server = server, _pattern = pattern,\r
464                         _sslctx = sslctx; -- parameters\r
465                         _usingssl = false;  -- client is using ssl;\r
466                 }\r
467                 if not sslctx then\r
468                         interface.starttls = false -- don't allow TLS\r
469                 end\r
470                 interface.id = tostring(interface):match("%x+$");\r
471                 interface.writecallback = function( event )  -- called on write events\r
472                         --vdebug( "new client write event, id/ip/port:", interface, ip, port )\r
473                         if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then  -- leave this event\r
474                                 --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )\r
475                                 interface.eventwrite = false\r
476                                 return -1\r
477                         end\r
478                         if EV_TIMEOUT == event then  -- took too long to write some data to socket -> disconnect\r
479                                 interface.fatalerror = "timeout during writing"\r
480                                 debug( "writing failed:", interface.fatalerror ) \r
481                                 interface:_close()\r
482                                 interface.eventwrite = false\r
483                                 return -1\r
484                         else  -- can write :)\r
485                                 if interface._usingssl then  -- handle luasec\r
486                                         if interface.eventreadtimeout then  -- we have to read first\r
487                                                 local ret = interface.readcallback( )  -- call readcallback\r
488                                                 --vdebug( "tried to read in writecallback, result:", ret )\r
489                                         end\r
490                                         if interface.eventwritetimeout then  -- luasec only\r
491                                                 interface.eventwritetimeout:close( )  -- first we have to close timeout event which where regged after a wantread error\r
492                                                 interface.eventwritetimeout = false\r
493                                         end\r
494                                 end\r
495                                 local succ, err, byte = interface.conn:send( interface.writebuffer, 1, interface.writebufferlen )\r
496                                 --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )\r
497                                 if succ then  -- writing succesful\r
498                                         interface.writebuffer = ""\r
499                                         interface.writebufferlen = 0\r
500                                         if interface.fatalerror then\r
501                                                 debug "closing client after writing"\r
502                                                 interface:_close()  -- close interface if needed\r
503                                         elseif interface.startsslcallback then  -- start ssl connection if needed\r
504                                                 debug "starting ssl handshake after writing"\r
505                                                 interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )\r
506                                         elseif interface.eventreadtimeout then\r
507                                                 return EV_WRITE, EV_TIMEOUT\r
508                                         end\r
509                                         interface.eventwrite = nil\r
510                                         return -1\r
511                                 elseif byte then  -- want write again\r
512                                         --vdebug( "writebuffer is not empty:", err )\r
513                                         interface.writebuffer = string_sub( interface.writebuffer, byte + 1, interface.writebufferlen )  -- new buffer\r
514                                         interface.writebufferlen = interface.writebufferlen - byte            \r
515                                         if "wantread" == err then  -- happens only with luasec\r
516                                                 local callback = function( )\r
517                                                         interface:_close()\r
518                                                         interface.eventwritetimeout = nil\r
519                                                         return evreturn, evtimeout\r
520                                                 end\r
521                                                 interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT )  -- reg a new timeout event\r
522                                                 debug( "wantread during write attemp, reg it in readcallback but dont know what really happens next..." )\r
523                                                 -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent\r
524                                                 return -1\r
525                                         end\r
526                                         return EV_WRITE, cfg.WRITE_TIMEOUT \r
527                                 else  -- connection was closed during writing or fatal error\r
528                                         interface.fatalerror = err or "fatal error"\r
529                                         debug( "connection failed in write event:", interface.fatalerror ) \r
530                                         interface:_close()\r
531                                         interface.eventwrite = nil\r
532                                         return -1\r
533                                 end\r
534                         end\r
535                 end\r
536                 \r
537                 interface.readcallback = function( event )  -- called on read events\r
538                         --vdebug( "new client read event, id/ip/port:", interface, ip, port )\r
539                         if interface.noreading or interface.fatalerror then  -- leave this event\r
540                                 --vdebug( "leaving this event because:", interface.noreading or interface.fatalerror )\r
541                                 interface.eventread = nil\r
542                                 return -1\r
543                         end\r
544                         if EV_TIMEOUT == event then  -- took too long to get some data from client -> disconnect\r
545                                 interface.fatalerror = "timeout during receiving"\r
546                                 debug( "connection failed:", interface.fatalerror ) \r
547                                 interface:_close()\r
548                                 interface.eventread = nil\r
549                                 return -1\r
550                         else -- can read\r
551                                 if interface._usingssl then  -- handle luasec\r
552                                         if interface.eventwritetimeout then  -- ok, in the past writecallback was regged\r
553                                                 local ret = interface.writecallback( )  -- call it\r
554                                                 --vdebug( "tried to write in readcallback, result:", ret )\r
555                                         end\r
556                                         if interface.eventreadtimeout then\r
557                                                 interface.eventreadtimeout:close( )\r
558                                                 interface.eventreadtimeout = nil\r
559                                         end\r
560                                 end\r
561                                 local buffer, err, part = interface.conn:receive( pattern )  -- receive buffer with "pattern"\r
562                                 --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )        \r
563                                 buffer = buffer or part or ""\r
564                                 local len = string_len( buffer )\r
565                                 if len > cfg.MAX_READ_LENGTH then  -- check buffer length\r
566                                         interface.fatalerror = "receive buffer exceeded"\r
567                                         debug( "fatal error:", interface.fatalerror )\r
568                                         interface:_close()\r
569                                         interface.eventread = nil\r
570                                         return -1\r
571                                 end\r
572                                 if err and ( err ~= "timeout" and err ~= "wantread" ) then\r
573                                         if "wantwrite" == err then -- need to read on write event\r
574                                                 if not interface.eventwrite then  -- register new write event if needed\r
575                                                         interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )\r
576                                                 end\r
577                                                 interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,\r
578                                                         function( )\r
579                                                                 interface:_close()\r
580                                                         end, cfg.READ_TIMEOUT\r
581                                                 )             \r
582                                                 debug( "wantwrite during read attemp, reg it in writecallback but dont know what really happens next..." )\r
583                                                 -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...\r
584                                         else  -- connection was closed or fatal error            \r
585                                                 interface.fatalerror = err\r
586                                                 debug( "connection failed in read event:", interface.fatalerror ) \r
587                                                 interface:_close()\r
588                                                 interface.eventread = nil\r
589                                                 return -1\r
590                                         end\r
591                                 end\r
592                                 interface.onincoming( interface, buffer, err )  -- send new data to listener\r
593                                 return EV_READ, cfg.READ_TIMEOUT\r
594                         end\r
595                 end\r
596 \r
597                 client:settimeout( 0 )  -- set non blocking\r
598                 setmetatable(interface, interface_mt)\r
599                 interfacelist( "add", interface )  -- add to interfacelist\r
600                 return interface\r
601         end\r
602 end\r
603 \r
604 local handleserver\r
605 do\r
606         function handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- creates an server interface\r
607                 debug "creating server interface..."\r
608                 local interface = {\r
609                         _connections = 0;\r
610                         \r
611                         conn = server;\r
612                         onconnect = listener.onconnect;  -- will be called when new client connected\r
613                         eventread = false;  -- read event handler\r
614                         eventclose = false; -- close event handler\r
615                         readcallback = false; -- read event callback\r
616                         fatalerror = false; -- error message\r
617                         nointerface = true;  -- lock/unlock parameter\r
618                 }\r
619                 interface.id = tostring(interface):match("%x+$");\r
620                 interface.readcallback = function( event )  -- server handler, called on incoming connections\r
621                         --vdebug( "server can accept, id/addr/port:", interface, addr, port )\r
622                         if interface.fatalerror then\r
623                                 --vdebug( "leaving this event because:", self.fatalerror )\r
624                                 interface.eventread = nil\r
625                                 return -1\r
626                         end\r
627                         local delay = cfg.ACCEPT_DELAY\r
628                         if EV_TIMEOUT == event then\r
629                                 if interface._connections >= cfg.MAX_CONNECTIONS then  -- check connection count\r
630                                         debug( "to many connections, seconds to wait for next accept:", delay )\r
631                                         return EV_TIMEOUT, delay  -- timeout...\r
632                                 else\r
633                                         return EV_READ  -- accept again\r
634                                 end\r
635                         end\r
636                         --vdebug("max connection check ok, accepting...")\r
637                         local client, err = server:accept()    -- try to accept; TODO: check err\r
638                         while client do\r
639                                 if interface._connections >= cfg.MAX_CONNECTIONS then\r
640                                         client:close( )  -- refuse connection\r
641                                         debug( "maximal connections reached, refuse client connection; accept delay:", delay )\r
642                                         return EV_TIMEOUT, delay  -- delay for next accept attemp\r
643                                 end\r
644                                 local ip, port = client:getpeername( )\r
645                                 interface._connections = interface._connections + 1  -- increase connection count\r
646                                 local clientinterface = handleclient( client, ip, port, interface, pattern, listener, nil, sslctx )\r
647                                 --vdebug( "client id:", clientinterface, "startssl:", startssl )\r
648                                 if startssl then\r
649                                         clientinterface:_start_ssl( clientinterface.onconnect )\r
650                                 else\r
651                                         clientinterface:_start_session( clientinterface.onconnect )\r
652                                 end\r
653                                 debug( "accepted incoming client connection from:", ip, port )\r
654                                 client, err = server:accept()    -- try to accept again\r
655                         end\r
656                         return EV_READ\r
657                 end\r
658                 \r
659                 server:settimeout( 0 )\r
660                 setmetatable(interface, interface_mt)\r
661                 interfacelist( "add", interface )\r
662                 interface:_start_session()\r
663                 return interface\r
664         end\r
665 end\r
666 \r
667 local addserver = ( function( )\r
668         return function( addr, port, listener, pattern, sslcfg, startssl )  -- TODO: check arguments\r
669                 --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil")\r
670                 local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket\r
671                 if not server then\r
672                         debug( "creating server socket failed because:", err )\r
673                         return nil, err\r
674                 end\r
675                 local sslctx\r
676                 if sslcfg then\r
677                         if not ssl then\r
678                                 debug "fatal error: luasec not found"\r
679                                 return nil, "luasec not found"\r
680                         end\r
681                         sslctx, err = ssl.newcontext( sslcfg )\r
682                         if err then\r
683                                 debug( "error while creating new ssl context for server socket:", err )\r
684                                 return nil, err\r
685                         end\r
686                 end      \r
687                 local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- new server handler\r
688                 debug( "new server created with id:", tostring(interface))\r
689                 return interface\r
690         end\r
691 end )( )\r
692 \r
693 local wrapclient = ( function( )\r
694         return function( client, addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )\r
695                 debug( "try to connect to:", addr, serverport, "with parameters:", pattern, localaddr, localport, sslcfg, startssl )\r
696                 local sslctx\r
697                 if sslcfg then  -- handle ssl/new context\r
698                         if not ssl then\r
699                                 debug "need luasec, but not available" \r
700                                 return nil, "luasec not found"\r
701                         end\r
702                         sslctx, err = ssl.newcontext( sslcfg )\r
703                         if err then\r
704                                 debug( "cannot create new ssl context:", err )\r
705                                 return nil, err\r
706                         end\r
707                 end\r
708         end\r
709 end )( )\r
710 \r
711 local addclient = ( function( )\r
712         return function( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )\r
713                 local client, err = socket.tcp()  -- creating new socket\r
714                 if not client then\r
715                         debug( "cannot create socket:", err ) \r
716                         return nil, err\r
717                 end\r
718                 client:settimeout( 0 )  -- set nonblocking\r
719                 if localaddr then\r
720                         local res, err = client:bind( localaddr, localport, -1 )\r
721                         if not res then\r
722                                 debug( "cannot bind client:", err )\r
723                                 return nil, err\r
724                         end\r
725                 end\r
726                 local res, err = client:connect( addr, serverport )  -- connect\r
727                 if res or ( err == "timeout" ) then\r
728                         local ip, port = client:getsockname( )\r
729                         local server = function( )\r
730                                 return nil, "this is a dummy server interface"\r
731                         end\r
732                         local interface = handleclient( client, ip, port, server, pattern, listener, sslctx )\r
733                         interface:_start_connection( startssl )\r
734                         debug( "new connection id:", interface )\r
735                         return interface, err\r
736                 else\r
737                         debug( "new connection failed:", err )\r
738                         return nil, err\r
739                 end\r
740                 return wrapclient( client, addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )    \r
741         end\r
742 end )( )\r
743 \r
744 local loop = function( )  -- starts the event loop\r
745         return base:loop( )\r
746 end\r
747 \r
748 local newevent = ( function( )\r
749         local add = base.addevent\r
750         return function( ... )\r
751                 return add( base, ... )\r
752         end\r
753 end )( )\r
754 \r
755 local closeallservers = function( arg )\r
756         for _, item in ipairs( interfacelist( ) ) do\r
757                 if item "type" == "server" then\r
758                         item( "close", arg )\r
759                 end\r
760         end\r
761 end\r
762 \r
763 return {\r
764 \r
765         cfg = cfg,\r
766         base = base,\r
767         loop = loop,\r
768         event = event,\r
769         event_base = base,\r
770         addevent = newevent,\r
771         addserver = addserver,\r
772         addclient = addclient,\r
773         wrapclient = wrapclient,\r
774         closeallservers = closeallservers,\r
775 \r
776         __NAME = SCRIPT_NAME,\r
777         __DATE = LAST_MODIFIED,\r
778         __AUTHOR = SCRIPT_AUTHOR,\r
779         __VERSION = SCRIPT_VERSION,\r
780 \r
781 }\r