450bd341517487b489cf946ba2e828158e82f13a
[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         \r
384         function interface_mt:set_send(new_send)\r
385                 -- No-op, we always use the underlying connection's send\r
386         end\r
387         \r
388         function interface_mt:starttls()\r
389                 debug( "try to start ssl at client id:", self.id )\r
390                 local err\r
391                 if not self._sslctx then  -- no ssl available\r
392                         err = "no ssl context available"\r
393                 elseif self._usingssl then  -- startssl was already called\r
394                         err = "ssl already active"\r
395                 end\r
396                 if err then\r
397                         debug( "error:", err )\r
398                         return nil, err      \r
399                 end\r
400                 self._usingssl = true\r
401                 self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event\r
402                         self.startsslcallback = nil\r
403                         self:_start_ssl();\r
404                         self.eventstarthandshake = nil\r
405                         return -1\r
406                 end\r
407                 if not self.eventwrite then\r
408                         self:_lock( true, true, true )  -- lock the interface, to not disturb the handshake\r
409                         self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 )  -- add event to start handshake\r
410                 else  -- wait until writebuffer is empty\r
411                         self:_lock( true, true, false )\r
412                         debug "ssl session delayed until writebuffer is empty..."\r
413                 end\r
414                 return true\r
415         end\r
416         \r
417         -- Stub handlers\r
418         function interface_mt:onconnect()\r
419         end\r
420         function interface_mt:onincoming()\r
421         end\r
422         function interface_mt:ondisconnect()\r
423         end\r
424         function interface_mt:ontimeout()\r
425         end\r
426 end                     \r
427 \r
428 -- End of client interface methods\r
429 \r
430 local handleclient;\r
431 do\r
432         local string_sub = string.sub  -- caching table lookups\r
433         local string_len = string.len\r
434         local addevent = base.addevent\r
435         local coroutine_wrap = coroutine.wrap\r
436         local socket_gettime = socket.gettime\r
437         local coroutine_yield = coroutine.yield\r
438         function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface\r
439                 --vdebug("creating client interfacce...")\r
440                 local interface = {\r
441                         type = "client";\r
442                         conn = client;\r
443                         currenttime = socket_gettime( );  -- safe the origin\r
444                         writebuffer = "";  -- writebuffer\r
445                         writebufferlen = 0;  -- length of writebuffer\r
446                         send = client.send;  -- caching table lookups\r
447                         receive = client.receive;\r
448                         onconnect = listener.onconnect;  -- will be called when client disconnects\r
449                         ondisconnect = listener.ondisconnect;  -- will be called when client disconnects\r
450                         onincoming = listener.onincoming;  -- will be called when client sends data\r
451                         ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs\r
452                         eventread = false, eventwrite = false, eventclose = false,\r
453                         eventhandshake = false, eventstarthandshake = false;  -- event handler\r
454                         eventconnect = false, eventsession = false;  -- more event handler...\r
455                         eventwritetimeout = false;  -- even more event handler...\r
456                         eventreadtimeout = false;\r
457                         fatalerror = false;  -- error message\r
458                         writecallback = false;  -- will be called on write events\r
459                         readcallback = false;  -- will be called on read events\r
460                         nointerface = true;  -- lock/unlock parameter of this interface\r
461                         noreading = false, nowriting = false;  -- locks of the read/writecallback\r
462                         startsslcallback = false;  -- starting handshake callback\r
463                         position = false;  -- position of client in interfacelist\r
464                         \r
465                         -- Properties\r
466                         _ip = ip, _port = port, _server = server, _pattern = pattern,\r
467                         _sslctx = sslctx; -- parameters\r
468                         _usingssl = false;  -- client is using ssl;\r
469                 }\r
470                 if not sslctx then\r
471                         interface.starttls = false -- don't allow TLS\r
472                 end\r
473                 interface.id = tostring(interface):match("%x+$");\r
474                 interface.writecallback = function( event )  -- called on write events\r
475                         --vdebug( "new client write event, id/ip/port:", interface, ip, port )\r
476                         if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then  -- leave this event\r
477                                 --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )\r
478                                 interface.eventwrite = false\r
479                                 return -1\r
480                         end\r
481                         if EV_TIMEOUT == event then  -- took too long to write some data to socket -> disconnect\r
482                                 interface.fatalerror = "timeout during writing"\r
483                                 debug( "writing failed:", interface.fatalerror ) \r
484                                 interface:_close()\r
485                                 interface.eventwrite = false\r
486                                 return -1\r
487                         else  -- can write :)\r
488                                 if interface._usingssl then  -- handle luasec\r
489                                         if interface.eventreadtimeout then  -- we have to read first\r
490                                                 local ret = interface.readcallback( )  -- call readcallback\r
491                                                 --vdebug( "tried to read in writecallback, result:", ret )\r
492                                         end\r
493                                         if interface.eventwritetimeout then  -- luasec only\r
494                                                 interface.eventwritetimeout:close( )  -- first we have to close timeout event which where regged after a wantread error\r
495                                                 interface.eventwritetimeout = false\r
496                                         end\r
497                                 end\r
498                                 local succ, err, byte = interface.conn:send( interface.writebuffer, 1, interface.writebufferlen )\r
499                                 --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )\r
500                                 if succ then  -- writing succesful\r
501                                         interface.writebuffer = ""\r
502                                         interface.writebufferlen = 0\r
503                                         if interface.fatalerror then\r
504                                                 debug "closing client after writing"\r
505                                                 interface:_close()  -- close interface if needed\r
506                                         elseif interface.startsslcallback then  -- start ssl connection if needed\r
507                                                 debug "starting ssl handshake after writing"\r
508                                                 interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )\r
509                                         elseif interface.eventreadtimeout then\r
510                                                 return EV_WRITE, EV_TIMEOUT\r
511                                         end\r
512                                         interface.eventwrite = nil\r
513                                         return -1\r
514                                 elseif byte then  -- want write again\r
515                                         --vdebug( "writebuffer is not empty:", err )\r
516                                         interface.writebuffer = string_sub( interface.writebuffer, byte + 1, interface.writebufferlen )  -- new buffer\r
517                                         interface.writebufferlen = interface.writebufferlen - byte            \r
518                                         if "wantread" == err then  -- happens only with luasec\r
519                                                 local callback = function( )\r
520                                                         interface:_close()\r
521                                                         interface.eventwritetimeout = nil\r
522                                                         return evreturn, evtimeout\r
523                                                 end\r
524                                                 interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT )  -- reg a new timeout event\r
525                                                 debug( "wantread during write attemp, reg it in readcallback but dont know what really happens next..." )\r
526                                                 -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent\r
527                                                 return -1\r
528                                         end\r
529                                         return EV_WRITE, cfg.WRITE_TIMEOUT \r
530                                 else  -- connection was closed during writing or fatal error\r
531                                         interface.fatalerror = err or "fatal error"\r
532                                         debug( "connection failed in write event:", interface.fatalerror ) \r
533                                         interface:_close()\r
534                                         interface.eventwrite = nil\r
535                                         return -1\r
536                                 end\r
537                         end\r
538                 end\r
539                 \r
540                 interface.readcallback = function( event )  -- called on read events\r
541                         --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )\r
542                         if interface.noreading or interface.fatalerror then  -- leave this event\r
543                                 --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )\r
544                                 interface.eventread = nil\r
545                                 return -1\r
546                         end\r
547                         if EV_TIMEOUT == event then  -- took too long to get some data from client -> disconnect\r
548                                 interface.fatalerror = "timeout during receiving"\r
549                                 debug( "connection failed:", interface.fatalerror ) \r
550                                 interface:_close()\r
551                                 interface.eventread = nil\r
552                                 return -1\r
553                         else -- can read\r
554                                 if interface._usingssl then  -- handle luasec\r
555                                         if interface.eventwritetimeout then  -- ok, in the past writecallback was regged\r
556                                                 local ret = interface.writecallback( )  -- call it\r
557                                                 --vdebug( "tried to write in readcallback, result:", tostring(ret) )\r
558                                         end\r
559                                         if interface.eventreadtimeout then\r
560                                                 interface.eventreadtimeout:close( )\r
561                                                 interface.eventreadtimeout = nil\r
562                                         end\r
563                                 end\r
564                                 local buffer, err, part = interface.conn:receive( pattern )  -- receive buffer with "pattern"\r
565                                 --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )        \r
566                                 buffer = buffer or part or ""\r
567                                 local len = string_len( buffer )\r
568                                 if len > cfg.MAX_READ_LENGTH then  -- check buffer length\r
569                                         interface.fatalerror = "receive buffer exceeded"\r
570                                         debug( "fatal error:", interface.fatalerror )\r
571                                         interface:_close()\r
572                                         interface.eventread = nil\r
573                                         return -1\r
574                                 end\r
575                                 if err and ( err ~= "timeout" and err ~= "wantread" ) then\r
576                                         if "wantwrite" == err then -- need to read on write event\r
577                                                 if not interface.eventwrite then  -- register new write event if needed\r
578                                                         interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )\r
579                                                 end\r
580                                                 interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,\r
581                                                         function( )\r
582                                                                 interface:_close()\r
583                                                         end, cfg.READ_TIMEOUT\r
584                                                 )             \r
585                                                 debug( "wantwrite during read attemp, reg it in writecallback but dont know what really happens next..." )\r
586                                                 -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...\r
587                                         else  -- connection was closed or fatal error            \r
588                                                 interface.fatalerror = err\r
589                                                 debug( "connection failed in read event:", interface.fatalerror ) \r
590                                                 interface:_close()\r
591                                                 interface.eventread = nil\r
592                                                 return -1\r
593                                         end\r
594                                 end\r
595                                 interface.onincoming( interface, buffer, err )  -- send new data to listener\r
596                                 return EV_READ, cfg.READ_TIMEOUT\r
597                         end\r
598                 end\r
599 \r
600                 client:settimeout( 0 )  -- set non blocking\r
601                 setmetatable(interface, interface_mt)\r
602                 interfacelist( "add", interface )  -- add to interfacelist\r
603                 return interface\r
604         end\r
605 end\r
606 \r
607 local handleserver\r
608 do\r
609         function handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- creates an server interface\r
610                 debug "creating server interface..."\r
611                 local interface = {\r
612                         _connections = 0;\r
613                         \r
614                         conn = server;\r
615                         onconnect = listener.onconnect;  -- will be called when new client connected\r
616                         eventread = false;  -- read event handler\r
617                         eventclose = false; -- close event handler\r
618                         readcallback = false; -- read event callback\r
619                         fatalerror = false; -- error message\r
620                         nointerface = true;  -- lock/unlock parameter\r
621                 }\r
622                 interface.id = tostring(interface):match("%x+$");\r
623                 interface.readcallback = function( event )  -- server handler, called on incoming connections\r
624                         --vdebug( "server can accept, id/addr/port:", interface, addr, port )\r
625                         if interface.fatalerror then\r
626                                 --vdebug( "leaving this event because:", self.fatalerror )\r
627                                 interface.eventread = nil\r
628                                 return -1\r
629                         end\r
630                         local delay = cfg.ACCEPT_DELAY\r
631                         if EV_TIMEOUT == event then\r
632                                 if interface._connections >= cfg.MAX_CONNECTIONS then  -- check connection count\r
633                                         debug( "to many connections, seconds to wait for next accept:", delay )\r
634                                         return EV_TIMEOUT, delay  -- timeout...\r
635                                 else\r
636                                         return EV_READ  -- accept again\r
637                                 end\r
638                         end\r
639                         --vdebug("max connection check ok, accepting...")\r
640                         local client, err = server:accept()    -- try to accept; TODO: check err\r
641                         while client do\r
642                                 if interface._connections >= cfg.MAX_CONNECTIONS then\r
643                                         client:close( )  -- refuse connection\r
644                                         debug( "maximal connections reached, refuse client connection; accept delay:", delay )\r
645                                         return EV_TIMEOUT, delay  -- delay for next accept attemp\r
646                                 end\r
647                                 local ip, port = client:getpeername( )\r
648                                 interface._connections = interface._connections + 1  -- increase connection count\r
649                                 local clientinterface = handleclient( client, ip, port, interface, pattern, listener, nil, sslctx )\r
650                                 --vdebug( "client id:", clientinterface, "startssl:", startssl )\r
651                                 if startssl then\r
652                                         clientinterface:_start_ssl( clientinterface.onconnect )\r
653                                 else\r
654                                         clientinterface:_start_session( clientinterface.onconnect )\r
655                                 end\r
656                                 debug( "accepted incoming client connection from:", ip, port )\r
657                                 client, err = server:accept()    -- try to accept again\r
658                         end\r
659                         return EV_READ\r
660                 end\r
661                 \r
662                 server:settimeout( 0 )\r
663                 setmetatable(interface, interface_mt)\r
664                 interfacelist( "add", interface )\r
665                 interface:_start_session()\r
666                 return interface\r
667         end\r
668 end\r
669 \r
670 local addserver = ( function( )\r
671         return function( addr, port, listener, pattern, sslcfg, startssl )  -- TODO: check arguments\r
672                 --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil")\r
673                 local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket\r
674                 if not server then\r
675                         debug( "creating server socket failed because:", err )\r
676                         return nil, err\r
677                 end\r
678                 local sslctx\r
679                 if sslcfg then\r
680                         if not ssl then\r
681                                 debug "fatal error: luasec not found"\r
682                                 return nil, "luasec not found"\r
683                         end\r
684                         sslctx, err = ssl.newcontext( sslcfg )\r
685                         if err then\r
686                                 debug( "error while creating new ssl context for server socket:", err )\r
687                                 return nil, err\r
688                         end\r
689                 end      \r
690                 local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- new server handler\r
691                 debug( "new server created with id:", tostring(interface))\r
692                 return interface\r
693         end\r
694 end )( )\r
695 \r
696 local addclient, wrapclient\r
697 do\r
698         function wrapclient( client, ip, port, listeners, pattern, sslctx, startssl )\r
699                 local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx )\r
700                 interface:_start_session()\r
701                 return interface\r
702                 --function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface\r
703         end\r
704         \r
705         function addclient( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )\r
706                 local client, err = socket.tcp()  -- creating new socket\r
707                 if not client then\r
708                         debug( "cannot create socket:", err ) \r
709                         return nil, err\r
710                 end\r
711                 client:settimeout( 0 )  -- set nonblocking\r
712                 if localaddr then\r
713                         local res, err = client:bind( localaddr, localport, -1 )\r
714                         if not res then\r
715                                 debug( "cannot bind client:", err )\r
716                                 return nil, err\r
717                         end\r
718                 end\r
719                 local sslctx\r
720                 if sslcfg then  -- handle ssl/new context\r
721                         if not ssl then\r
722                                 debug "need luasec, but not available" \r
723                                 return nil, "luasec not found"\r
724                         end\r
725                         sslctx, err = ssl.newcontext( sslcfg )\r
726                         if err then\r
727                                 debug( "cannot create new ssl context:", err )\r
728                                 return nil, err\r
729                         end\r
730                 end\r
731                 local res, err = client:connect( addr, serverport )  -- connect\r
732                 if res or ( err == "timeout" ) then\r
733                         local ip, port = client:getsockname( )\r
734                         local server = function( )\r
735                                 return nil, "this is a dummy server interface"\r
736                         end\r
737                         local interface = wrapclient( client, ip, serverport, listeners, pattern, sslctx, startssl )\r
738                         interface:_start_connection( startssl )\r
739                         debug( "new connection id:", interface.id )\r
740                         return interface, err\r
741                 else\r
742                         debug( "new connection failed:", err )\r
743                         return nil, err\r
744                 end\r
745         end\r
746 end\r
747 \r
748 \r
749 local loop = function( )  -- starts the event loop\r
750         return base:loop( )\r
751 end\r
752 \r
753 local newevent = ( function( )\r
754         local add = base.addevent\r
755         return function( ... )\r
756                 return add( base, ... )\r
757         end\r
758 end )( )\r
759 \r
760 local closeallservers = function( arg )\r
761         for _, item in ipairs( interfacelist( ) ) do\r
762                 if item "type" == "server" then\r
763                         item( "close", arg )\r
764                 end\r
765         end\r
766 end\r
767 \r
768 return {\r
769 \r
770         cfg = cfg,\r
771         base = base,\r
772         loop = loop,\r
773         event = event,\r
774         event_base = base,\r
775         addevent = newevent,\r
776         addserver = addserver,\r
777         addclient = addclient,\r
778         wrapclient = wrapclient,\r
779         closeallservers = closeallservers,\r
780 \r
781         __NAME = SCRIPT_NAME,\r
782         __DATE = LAST_MODIFIED,\r
783         __AUTHOR = SCRIPT_AUTHOR,\r
784         __VERSION = SCRIPT_VERSION,\r
785 \r
786 }\r