ce2c74bb454506f6679ad6aab3a4677f5f885a18
[prosody.git] / net / server.lua
1 -- \r
2 -- server.lua by blastbeat of the luadch project\r
3 -- Re-used here under the MIT/X Consortium License\r
4 -- \r
5 -- Modifications (C) 2008-2009 Matthew Wild, Waqas Hussain\r
6 --\r
7 \r
8 -- // wrapping luadch stuff // --\r
9 \r
10 local use = function( what )\r
11     return _G[ what ]\r
12 end\r
13 local clean = function( tbl )\r
14     for i, k in pairs( tbl ) do\r
15         tbl[ i ] = nil\r
16     end\r
17 end\r
18 \r
19 local log, table_concat = require ("util.logger").init("socket"), table.concat;\r
20 local out_put = function (...) return log("debug", table_concat{...}); end\r
21 local out_error = function (...) return log("warn", table_concat{...}); end\r
22 local mem_free = collectgarbage\r
23 \r
24 ----------------------------------// DECLARATION //--\r
25 \r
26 --// constants //--\r
27 \r
28 local STAT_UNIT = 1    -- byte\r
29 \r
30 --// lua functions //--\r
31 \r
32 local type = use "type"\r
33 local pairs = use "pairs"\r
34 local ipairs = use "ipairs"\r
35 local tostring = use "tostring"\r
36 local collectgarbage = use "collectgarbage"\r
37 \r
38 --// lua libs //--\r
39 \r
40 local os = use "os"\r
41 local table = use "table"\r
42 local string = use "string"\r
43 local coroutine = use "coroutine"\r
44 \r
45 --// lua lib methods //--\r
46 \r
47 local os_time = os.time\r
48 local os_difftime = os.difftime\r
49 local table_concat = table.concat\r
50 local table_remove = table.remove\r
51 local string_len = string.len\r
52 local string_sub = string.sub\r
53 local coroutine_wrap = coroutine.wrap\r
54 local coroutine_yield = coroutine.yield\r
55 \r
56 --// extern libs //--\r
57 \r
58 local luasec = select( 2, pcall( require, "ssl" ) )\r
59 local luasocket = require "socket"\r
60 \r
61 --// extern lib methods //--\r
62 \r
63 local ssl_wrap = ( luasec and luasec.wrap )\r
64 local socket_bind = luasocket.bind\r
65 local socket_sleep = luasocket.sleep\r
66 local socket_select = luasocket.select\r
67 local ssl_newcontext = ( luasec and luasec.newcontext )\r
68 \r
69 --// functions //--\r
70 \r
71 local id\r
72 local loop\r
73 local stats\r
74 local idfalse\r
75 local addtimer\r
76 local closeall\r
77 local addserver\r
78 local getserver\r
79 local wrapserver\r
80 local getsettings\r
81 local closesocket\r
82 local removesocket\r
83 local removeserver\r
84 local changetimeout\r
85 local wrapconnection\r
86 local changesettings\r
87 \r
88 --// tables //--\r
89 \r
90 local _server\r
91 local _readlist\r
92 local _timerlist\r
93 local _sendlist\r
94 local _socketlist\r
95 local _closelist\r
96 local _readtimes\r
97 local _writetimes\r
98 \r
99 --// simple data types //--\r
100 \r
101 local _\r
102 local _readlistlen\r
103 local _sendlistlen\r
104 local _timerlistlen\r
105 \r
106 local _sendtraffic\r
107 local _readtraffic\r
108 \r
109 local _selecttimeout\r
110 local _sleeptime\r
111 \r
112 local _starttime\r
113 local _currenttime\r
114 \r
115 local _maxsendlen\r
116 local _maxreadlen\r
117 \r
118 local _checkinterval\r
119 local _sendtimeout\r
120 local _readtimeout\r
121 \r
122 local _cleanqueue\r
123 \r
124 local _timer\r
125 \r
126 local _maxclientsperserver\r
127 \r
128 ----------------------------------// DEFINITION //--\r
129 \r
130 _server = { }    -- key = port, value = table; list of listening servers\r
131 _readlist = { }    -- array with sockets to read from\r
132 _sendlist = { }    -- arrary with sockets to write to\r
133 _timerlist = { }    -- array of timer functions\r
134 _socketlist = { }    -- key = socket, value = wrapped socket (handlers)\r
135 _readtimes = { }   -- key = handler, value = timestamp of last data reading\r
136 _writetimes = { }   -- key = handler, value = timestamp of last data writing/sending\r
137 _closelist = { }    -- handlers to close\r
138 \r
139 _readlistlen = 0    -- length of readlist\r
140 _sendlistlen = 0    -- length of sendlist\r
141 _timerlistlen = 0    -- lenght of timerlist\r
142 \r
143 _sendtraffic = 0    -- some stats\r
144 _readtraffic = 0\r
145 \r
146 _selecttimeout = 1    -- timeout of socket.select\r
147 _sleeptime = 0    -- time to wait at the end of every loop\r
148 \r
149 _maxsendlen = 51000 * 1024    -- max len of send buffer\r
150 _maxreadlen = 25000 * 1024    -- max len of read buffer\r
151 \r
152 _checkinterval = 1200000    -- interval in secs to check idle clients\r
153 _sendtimeout = 60000    -- allowed send idle time in secs\r
154 _readtimeout = 6 * 60 * 60    -- allowed read idle time in secs\r
155 \r
156 _cleanqueue = false    -- clean bufferqueue after using\r
157 \r
158 _maxclientsperserver = 1000\r
159 \r
160 ----------------------------------// PRIVATE //--\r
161 \r
162 wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server\r
163 \r
164     maxconnections = maxconnections or _maxclientsperserver\r
165 \r
166     local connections = 0\r
167 \r
168     local dispatch, disconnect = listeners.incoming or listeners.listener, listeners.disconnect\r
169 \r
170     local err\r
171 \r
172     local ssl = false\r
173 \r
174     if sslctx then\r
175         ssl = true\r
176         if not ssl_newcontext then\r
177             out_error "luasec not found"\r
178             ssl = false\r
179         end\r
180         if type( sslctx ) ~= "table" then\r
181             out_error "server.lua: wrong server sslctx"\r
182             ssl = false\r
183         end\r
184         local ctx;\r
185         ctx, err = ssl_newcontext( sslctx )\r
186         if not ctx then\r
187             err = err or "wrong sslctx parameters"\r
188             local file;\r
189             file = err:match("^error loading (.-) %(");\r
190             if file then\r
191                 if file == "private key" then\r
192                         file = sslctx.key or "your private key";\r
193                 elseif file == "certificate" then\r
194                         file = sslctx.certificate or "your certificate file";\r
195                 end\r
196                 local reason = err:match("%((.+)%)$") or "some reason";\r
197                 if reason == "Permission denied" then\r
198                         reason = "Check that the permissions allow Prosody to read this file.";\r
199                 elseif reason == "No such file or directory" then\r
200                         reason = "Check that the path is correct, and the file exists.";\r
201                 elseif reason == "system lib" then\r
202                         reason = "Previous error (see logs), or other system error.";\r
203                 else\r
204                         reason = "Reason: "..tostring(reason or "unknown"):lower();\r
205                 end\r
206                 log("error", "SSL/TLS: Failed to load %s: %s", file, reason);\r
207             else\r
208                 log("error", "SSL/TLS: Error initialising for port %d: %s", serverport, err );\r
209             end\r
210             ssl = false\r
211         end\r
212         sslctx = ctx;\r
213     end\r
214     if not ssl then\r
215       sslctx = false;\r
216       if startssl then\r
217          log("error", "Failed to listen on port %d due to SSL/TLS to SSL/TLS initialisation errors (see logs)", serverport )\r
218          return nil, "Cannot start ssl,  see log for details"\r
219        end\r
220     end\r
221 \r
222     local accept = socket.accept\r
223 \r
224     --// public methods of the object //--\r
225 \r
226     local handler = { }\r
227 \r
228     handler.shutdown = function( ) end\r
229 \r
230     handler.ssl = function( )\r
231         return ssl\r
232     end\r
233     handler.sslctx = function( )\r
234         return sslctx\r
235     end\r
236     handler.remove = function( )\r
237         connections = connections - 1\r
238     end\r
239     handler.close = function( )\r
240         for _, handler in pairs( _socketlist ) do\r
241             if handler.serverport == serverport then\r
242                 handler.disconnect( handler, "server closed" )\r
243                 handler.close( true )\r
244             end\r
245         end\r
246         socket:close( )\r
247         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
248         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
249         _socketlist[ socket ] = nil\r
250         handler = nil\r
251         socket = nil\r
252         --mem_free( )\r
253         out_put "server.lua: closed server handler and removed sockets from list"\r
254     end\r
255     handler.ip = function( )\r
256         return ip\r
257     end\r
258     handler.serverport = function( )\r
259         return serverport\r
260     end\r
261     handler.socket = function( )\r
262         return socket\r
263     end\r
264     handler.readbuffer = function( )\r
265         if connections > maxconnections then\r
266             out_put( "server.lua: refused new client connection: server full" )\r
267             return false\r
268         end\r
269         local client, err = accept( socket )    -- try to accept\r
270         if client then\r
271             local ip, clientport = client:getpeername( )\r
272             client:settimeout( 0 )\r
273             local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, startssl )    -- wrap new client socket\r
274             if err then    -- error while wrapping ssl socket\r
275                 return false\r
276             end\r
277             connections = connections + 1\r
278             out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))\r
279             return dispatch( handler )\r
280         elseif err then    -- maybe timeout or something else\r
281             out_put( "server.lua: error with new client connection: ", tostring(err) )\r
282             return false\r
283         end\r
284     end\r
285     return handler\r
286 end\r
287 \r
288 wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, startssl )    -- this function wraps a client to a handler object\r
289 \r
290     socket:settimeout( 0 )\r
291 \r
292     --// local import of socket methods //--\r
293 \r
294     local send\r
295     local receive\r
296     local shutdown\r
297 \r
298     --// private closures of the object //--\r
299 \r
300     local ssl\r
301 \r
302     local dispatch = listeners.incoming or listeners.listener\r
303     local disconnect = listeners.disconnect\r
304 \r
305     local bufferqueue = { }    -- buffer array\r
306     local bufferqueuelen = 0    -- end of buffer array\r
307 \r
308     local toclose\r
309     local fatalerror\r
310     local needtls\r
311 \r
312     local bufferlen = 0\r
313 \r
314     local noread = false\r
315     local nosend = false\r
316 \r
317     local sendtraffic, readtraffic = 0, 0\r
318 \r
319     local maxsendlen = _maxsendlen\r
320     local maxreadlen = _maxreadlen\r
321 \r
322     --// public methods of the object //--\r
323 \r
324     local handler = bufferqueue    -- saves a table ^_^\r
325 \r
326     handler.dispatch = function( )\r
327         return dispatch\r
328     end\r
329     handler.disconnect = function( )\r
330         return disconnect\r
331     end\r
332     handler.setlistener = function( listeners )\r
333         dispatch = listeners.incoming\r
334         disconnect = listeners.disconnect\r
335     end\r
336     handler.getstats = function( )\r
337         return readtraffic, sendtraffic\r
338     end\r
339     handler.ssl = function( )\r
340         return ssl\r
341     end\r
342     handler.send = function( _, data, i, j )\r
343         return send( socket, data, i, j )\r
344     end\r
345     handler.receive = function( pattern, prefix )\r
346         return receive( socket, pattern, prefix )\r
347     end\r
348     handler.shutdown = function( pattern )\r
349         return shutdown( socket, pattern )\r
350     end\r
351     handler.close = function( forced )\r
352         if not handler then return true; end\r
353         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
354         _readtimes[ handler ] = nil\r
355         if bufferqueuelen ~= 0 then\r
356             if not ( forced or fatalerror ) then\r
357                 handler.sendbuffer( )\r
358                 if bufferqueuelen ~= 0 then   -- try again...\r
359                     if handler then\r
360                         handler.write = nil    -- ... but no further writing allowed\r
361                     end\r
362                     toclose = true\r
363                     return false\r
364                 end\r
365             else\r
366                 send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send\r
367             end\r
368         end\r
369         if not handler then return true; end\r
370         _ = shutdown and shutdown( socket )\r
371         socket:close( )\r
372         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
373         _socketlist[ socket ] = nil\r
374         if handler then\r
375             _writetimes[ handler ] = nil\r
376             _closelist[ handler ] = nil\r
377             handler = nil\r
378         end\r
379         socket = nil\r
380         --mem_free( )\r
381         if server then\r
382                 server.remove( )\r
383         end\r
384         out_put "server.lua: closed client handler and removed socket from list"\r
385         return true\r
386     end\r
387     handler.ip = function( )\r
388         return ip\r
389     end\r
390     handler.serverport = function( )\r
391         return serverport\r
392     end\r
393     handler.clientport = function( )\r
394         return clientport\r
395     end\r
396     local write = function( data )\r
397         bufferlen = bufferlen + string_len( data )\r
398         if bufferlen > maxsendlen then\r
399             _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle\r
400             handler.write = idfalse    -- dont write anymore\r
401             return false\r
402         elseif socket and not _sendlist[ socket ] then\r
403             _sendlistlen = _sendlistlen + 1\r
404             _sendlist[ _sendlistlen ] = socket\r
405             _sendlist[ socket ] = _sendlistlen\r
406         end\r
407         bufferqueuelen = bufferqueuelen + 1\r
408         bufferqueue[ bufferqueuelen ] = data\r
409         if handler then\r
410                 _writetimes[ handler ] = _writetimes[ handler ] or _currenttime\r
411         end\r
412         return true\r
413     end\r
414     handler.write = write\r
415     handler.bufferqueue = function( )\r
416         return bufferqueue\r
417     end\r
418     handler.socket = function( )\r
419         return socket\r
420     end\r
421     handler.pattern = function( new )\r
422         pattern = new or pattern\r
423         return pattern\r
424     end\r
425     handler.setsend = function ( newsend )\r
426         send = newsend or send\r
427         return send\r
428     end\r
429     handler.bufferlen = function( readlen, sendlen )\r
430         maxsendlen = sendlen or maxsendlen\r
431         maxreadlen = readlen or maxreadlen\r
432         return maxreadlen, maxsendlen\r
433     end\r
434     handler.lock = function( switch )\r
435         if switch == true then\r
436             handler.write = idfalse\r
437             local tmp = _sendlistlen\r
438             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
439             _writetimes[ handler ] = nil\r
440             if _sendlistlen ~= tmp then\r
441                 nosend = true\r
442             end\r
443             tmp = _readlistlen\r
444             _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
445             _readtimes[ handler ] = nil\r
446             if _readlistlen ~= tmp then\r
447                 noread = true\r
448             end\r
449         elseif switch == false then\r
450             handler.write = write\r
451             if noread then\r
452                 noread = false\r
453                 _readlistlen = _readlistlen + 1\r
454                 _readlist[ socket ] = _readlistlen\r
455                 _readlist[ _readlistlen ] = socket\r
456                 _readtimes[ handler ] = _currenttime\r
457             end\r
458             if nosend then\r
459                 nosend = false\r
460                 write( "" )\r
461             end\r
462         end\r
463         return noread, nosend\r
464     end\r
465     local _readbuffer = function( )    -- this function reads data\r
466         local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"\r
467         if not err or ( err == "timeout" or err == "wantread" ) then    -- received something\r
468             local buffer = buffer or part or ""\r
469             local len = string_len( buffer )\r
470             if len > maxreadlen then\r
471                 disconnect( handler, "receive buffer exceeded" )\r
472                 handler.close( true )\r
473                 return false\r
474             end\r
475             local count = len * STAT_UNIT\r
476             readtraffic = readtraffic + count\r
477             _readtraffic = _readtraffic + count\r
478             _readtimes[ handler ] = _currenttime\r
479             --out_put( "server.lua: read data '", buffer, "', error: ", err )\r
480             return dispatch( handler, buffer, err )\r
481         else    -- connections was closed or fatal error\r
482             out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )\r
483             fatalerror = true\r
484             disconnect( handler, err )\r
485             _ = handler and handler.close( )\r
486             return false\r
487         end\r
488     end\r
489     local _sendbuffer = function( )    -- this function sends data\r
490         local succ, err, byte, buffer, count;\r
491         local count;\r
492         if socket then\r
493             buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )\r
494             succ, err, byte = send( socket, buffer, 1, bufferlen )\r
495             count = ( succ or byte or 0 ) * STAT_UNIT\r
496             sendtraffic = sendtraffic + count\r
497             _sendtraffic = _sendtraffic + count\r
498             _ = _cleanqueue and clean( bufferqueue )\r
499             --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )\r
500         else\r
501             succ, err, count = false, "closed", 0;\r
502         end\r
503         if succ then    -- sending succesful\r
504             bufferqueuelen = 0\r
505             bufferlen = 0\r
506             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist\r
507             _ = needtls and handler.starttls(true)\r
508             _writetimes[ handler ] = nil\r
509             _ = toclose and handler.close( )\r
510             return true\r
511         elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write\r
512             buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer\r
513             bufferqueue[ 1 ] = buffer    -- insert new buffer in queue\r
514             bufferqueuelen = 1\r
515             bufferlen = bufferlen - byte\r
516             _writetimes[ handler ] = _currenttime\r
517             return true\r
518         else    -- connection was closed during sending or fatal error\r
519             out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )\r
520             fatalerror = true\r
521             disconnect( handler, err )\r
522             _ = handler and handler.close( )\r
523             return false\r
524         end\r
525     end\r
526 \r
527     if sslctx then    -- ssl?\r
528         ssl = true\r
529         local wrote\r
530         local read\r
531         local handshake = coroutine_wrap( function( client )    -- create handshake coroutine\r
532                 local err\r
533                 for i = 1, 10 do    -- 10 handshake attemps\r
534                     _sendlistlen = ( wrote and removesocket( _sendlist, socket, _sendlistlen ) ) or _sendlistlen\r
535                     _readlistlen = ( read and removesocket( _readlist, socket, _readlistlen ) ) or _readlistlen\r
536                     read, wrote = nil, nil\r
537                     _, err = client:dohandshake( )\r
538                     if not err then\r
539                         out_put( "server.lua: ssl handshake done" )\r
540                         handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions\r
541                         handler.sendbuffer = _sendbuffer\r
542                         -- return dispatch( handler )\r
543                         return true\r
544                     else\r
545                         out_put( "server.lua: error during ssl handshake: ", tostring(err) )\r
546                         if err == "wantwrite" and not wrote then\r
547                             _sendlistlen = _sendlistlen + 1\r
548                             _sendlist[ _sendlistlen ] = client\r
549                             wrote = true\r
550                         elseif err == "wantread" and not read then\r
551                                 _readlistlen = _readlistlen + 1\r
552                                 _readlist [ _readlistlen ] = client\r
553                                 read = true\r
554                         else\r
555                                 break;\r
556                         end\r
557                         --coroutine_yield( handler, nil, err )    -- handshake not finished\r
558                         coroutine_yield( )\r
559                     end\r
560                 end\r
561                 disconnect( handler, "ssl handshake failed" )\r
562                 _ = handler and handler.close( true )    -- forced disconnect\r
563                 return false    -- handshake failed\r
564             end\r
565         )\r
566         if startssl then    -- ssl now?\r
567             --out_put("server.lua: ", "starting ssl handshake")\r
568             local err\r
569             socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
570             if err then\r
571                 out_put( "server.lua: ssl error: ", tostring(err) )\r
572                 --mem_free( )\r
573                 return nil, nil, err    -- fatal error\r
574             end\r
575             socket:settimeout( 0 )\r
576             handler.readbuffer = handshake\r
577             handler.sendbuffer = handshake\r
578             handshake( socket ) -- do handshake\r
579             if not socket then\r
580                 return nil, nil, "ssl handshake failed";\r
581             end\r
582         else\r
583             -- We're not automatically doing SSL, so we're not secure (yet)\r
584             ssl = false\r
585             handler.starttls = function( now )\r
586                 if not now then\r
587                     --out_put "server.lua: we need to do tls, but delaying until later"\r
588                     needtls = true\r
589                     return\r
590                 end\r
591                 --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
592                 local oldsocket, err = socket\r
593                 socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
594                 --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
595                 if err then\r
596                     out_put( "server.lua: error while starting tls on client: ", tostring(err) )\r
597                     return nil, err    -- fatal error\r
598                 end\r
599 \r
600                 socket:settimeout( 0 )\r
601 \r
602                 -- add the new socket to our system\r
603 \r
604                 send = socket.send\r
605                 receive = socket.receive\r
606                 shutdown = id\r
607 \r
608                 _socketlist[ socket ] = handler\r
609                 _readlistlen = _readlistlen + 1\r
610                 _readlist[ _readlistlen ] = socket\r
611                 _readlist[ socket ] = _readlistlen\r
612 \r
613                 -- remove traces of the old socket\r
614 \r
615                 _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
616                 _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
617                 _socketlist[ oldsocket ] = nil\r
618 \r
619                 handler.starttls = nil\r
620                 needtls = nil\r
621                 \r
622                 -- Secure now\r
623                 ssl = true\r
624 \r
625                 handler.readbuffer = handshake\r
626                 handler.sendbuffer = handshake\r
627                 handshake( socket )    -- do handshake\r
628             end\r
629             handler.readbuffer = _readbuffer\r
630             handler.sendbuffer = _sendbuffer\r
631         end\r
632     else    -- normal connection\r
633         ssl = false\r
634         handler.readbuffer = _readbuffer\r
635         handler.sendbuffer = _sendbuffer\r
636     end\r
637 \r
638     send = socket.send\r
639     receive = socket.receive\r
640     shutdown = ( ssl and id ) or socket.shutdown\r
641 \r
642     _socketlist[ socket ] = handler\r
643     _readlistlen = _readlistlen + 1\r
644     _readlist[ _readlistlen ] = socket\r
645     _readlist[ socket ] = _readlistlen\r
646 \r
647     return handler, socket\r
648 end\r
649 \r
650 id = function( )\r
651 end\r
652 \r
653 idfalse = function( )\r
654     return false\r
655 end\r
656 \r
657 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
658     local pos = list[ socket ]\r
659     if pos then\r
660         list[ socket ] = nil\r
661         local last = list[ len ]\r
662         list[ len ] = nil\r
663         if last ~= socket then\r
664             list[ last ] = pos\r
665             list[ pos ] = last\r
666         end\r
667         return len - 1\r
668     end\r
669     return len\r
670 end\r
671 \r
672 closesocket = function( socket )\r
673     _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
674     _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
675     _socketlist[ socket ] = nil\r
676     socket:close( )\r
677     --mem_free( )\r
678 end\r
679 \r
680 ----------------------------------// PUBLIC //--\r
681 \r
682 addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
683     local err\r
684     --out_put("server.lua: autossl on ", port, " is ", startssl)\r
685     if type( listeners ) ~= "table" then\r
686         err = "invalid listener table"\r
687     end\r
688     if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
689         err = "invalid port"\r
690     elseif _server[ port ] then\r
691         err =  "listeners on port '" .. port .. "' already exist"\r
692     elseif sslctx and not luasec then\r
693         err = "luasec not found"\r
694     end\r
695     if err then\r
696         out_error( "server.lua, port ", port, ": ", err )\r
697         return nil, err\r
698     end\r
699     addr = addr or "*"\r
700     local server, err = socket_bind( addr, port )\r
701     if err then\r
702         out_error( "server.lua, port ", port, ": ", err )\r
703         return nil, err\r
704     end\r
705     local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
706     if not handler then\r
707         server:close( )\r
708         return nil, err\r
709     end\r
710     server:settimeout( 0 )\r
711     _readlistlen = _readlistlen + 1\r
712     _readlist[ _readlistlen ] = server\r
713     _server[ port ] = handler\r
714     _socketlist[ server ] = handler\r
715     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
716     return handler\r
717 end\r
718 \r
719 getserver = function ( port )\r
720         return _server[ port ];\r
721 end\r
722 \r
723 removeserver = function( port )\r
724     local handler = _server[ port ]\r
725     if not handler then\r
726         return nil, "no server found on port '" .. tostring( port ) .. "'"\r
727     end\r
728     handler.close( )\r
729     _server[ port ] = nil\r
730     return true\r
731 end\r
732 \r
733 closeall = function( )\r
734     for _, handler in pairs( _socketlist ) do\r
735         handler.close( )\r
736         _socketlist[ _ ] = nil\r
737     end\r
738     _readlistlen = 0\r
739     _sendlistlen = 0\r
740     _timerlistlen = 0\r
741     _server = { }\r
742     _readlist = { }\r
743     _sendlist = { }\r
744     _timerlist = { }\r
745     _socketlist = { }\r
746     --mem_free( )\r
747 end\r
748 \r
749 getsettings = function( )\r
750     return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver\r
751 end\r
752 \r
753 changesettings = function( new )\r
754     if type( new ) ~= "table" then\r
755         return nil, "invalid settings table"\r
756     end\r
757     _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
758     _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
759     _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
760     _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
761     _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
762     _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
763     _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
764     _cleanqueue = new.cleanqueue\r
765     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
766     return true\r
767 end\r
768 \r
769 addtimer = function( listener )\r
770     if type( listener ) ~= "function" then\r
771         return nil, "invalid listener function"\r
772     end\r
773     _timerlistlen = _timerlistlen + 1\r
774     _timerlist[ _timerlistlen ] = listener\r
775     return true\r
776 end\r
777 \r
778 stats = function( )\r
779     return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
780 end\r
781 \r
782 local dontstop = true; -- thinking about tomorrow, ...\r
783 \r
784 setquitting = function (quit)\r
785         dontstop = not quit;\r
786         return;\r
787 end\r
788 \r
789 loop = function( )    -- this is the main loop of the program\r
790     while dontstop do\r
791         local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
792         for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
793             local handler = _socketlist[ socket ]\r
794             if handler then\r
795                 handler.sendbuffer( )\r
796             else\r
797                 closesocket( socket )\r
798                 out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
799             end\r
800         end\r
801         for i, socket in ipairs( read ) do    -- receive data\r
802             local handler = _socketlist[ socket ]\r
803             if handler then\r
804                 handler.readbuffer( )\r
805             else\r
806                 closesocket( socket )\r
807                 out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
808             end\r
809         end\r
810         for handler, err in pairs( _closelist ) do\r
811             handler.disconnect( )( handler, err )\r
812             handler.close( true )    -- forced disconnect\r
813         end\r
814         clean( _closelist )\r
815         _currenttime = os_time( )\r
816         if os_difftime( _currenttime - _timer ) >= 1 then\r
817             for i = 1, _timerlistlen do\r
818                 _timerlist[ i ]( )    -- fire timers\r
819             end\r
820             _timer = _currenttime\r
821         end\r
822         socket_sleep( _sleeptime )    -- wait some time\r
823         --collectgarbage( )\r
824     end\r
825     return "quitting"\r
826 end\r
827 \r
828 --// EXPERIMENTAL //--\r
829 \r
830 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
831     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
832     _socketlist[ socket ] = handler\r
833     _sendlistlen = _sendlistlen + 1\r
834     _sendlist[ _sendlistlen ] = socket\r
835     _sendlist[ socket ] = _sendlistlen\r
836     return handler, socket\r
837 end\r
838 \r
839 local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
840     local client, err = luasocket.tcp( )\r
841     if err then\r
842         return nil, err\r
843     end\r
844     client:settimeout( 0 )\r
845     _, err = client:connect( address, port )\r
846     if err then    -- try again\r
847         local handler = wrapclient( client, address, port, listeners )\r
848     else\r
849         wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )\r
850     end\r
851 end\r
852 \r
853 --// EXPERIMENTAL //--\r
854 \r
855 ----------------------------------// BEGIN //--\r
856 \r
857 use "setmetatable" ( _socketlist, { __mode = "k" } )\r
858 use "setmetatable" ( _readtimes, { __mode = "k" } )\r
859 use "setmetatable" ( _writetimes, { __mode = "k" } )\r
860 \r
861 _timer = os_time( )\r
862 _starttime = os_time( )\r
863 \r
864 addtimer( function( )\r
865         local difftime = os_difftime( _currenttime - _starttime )\r
866         if difftime > _checkinterval then\r
867             _starttime = _currenttime\r
868             for handler, timestamp in pairs( _writetimes ) do\r
869                 if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
870                     --_writetimes[ handler ] = nil\r
871                     handler.disconnect( )( handler, "send timeout" )\r
872                     handler.close( true )    -- forced disconnect\r
873                 end\r
874             end\r
875             for handler, timestamp in pairs( _readtimes ) do\r
876                 if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
877                     --_readtimes[ handler ] = nil\r
878                     handler.disconnect( )( handler, "read timeout" )\r
879                     handler.close( )    -- forced disconnect?\r
880                 end\r
881             end\r
882         end\r
883     end\r
884 )\r
885 \r
886 ----------------------------------// PUBLIC INTERFACE //--\r
887 \r
888 return {\r
889 \r
890     addclient = addclient,\r
891     wrapclient = wrapclient,\r
892     \r
893     loop = loop,\r
894     stats = stats,\r
895     closeall = closeall,\r
896     addtimer = addtimer,\r
897     addserver = addserver,\r
898     getserver = getserver,\r
899     getsettings = getsettings,\r
900     setquitting = setquitting,\r
901     removeserver = removeserver,\r
902     changesettings = changesettings,\r
903 }\r