971ea5530695f191cfeff18dcc1c9b3c93eae3fe
[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.remove = function( )\r
234         connections = connections - 1\r
235     end\r
236     handler.close = function( )\r
237         for _, handler in pairs( _socketlist ) do\r
238             if handler.serverport == serverport then\r
239                 handler.disconnect( handler, "server closed" )\r
240                 handler.close( true )\r
241             end\r
242         end\r
243         socket:close( )\r
244         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
245         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
246         _socketlist[ socket ] = nil\r
247         handler = nil\r
248         socket = nil\r
249         mem_free( )\r
250         out_put "server.lua: closed server handler and removed sockets from list"\r
251     end\r
252     handler.ip = function( )\r
253         return ip\r
254     end\r
255     handler.serverport = function( )\r
256         return serverport\r
257     end\r
258     handler.socket = function( )\r
259         return socket\r
260     end\r
261     handler.readbuffer = function( )\r
262         if connections > maxconnections then\r
263             out_put( "server.lua: refused new client connection: server full" )\r
264             return false\r
265         end\r
266         local client, err = accept( socket )    -- try to accept\r
267         if client then\r
268             local ip, clientport = client:getpeername( )\r
269             client:settimeout( 0 )\r
270             local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, startssl )    -- wrap new client socket\r
271             if err then    -- error while wrapping ssl socket\r
272                 return false\r
273             end\r
274             connections = connections + 1\r
275             out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))\r
276             return dispatch( handler )\r
277         elseif err then    -- maybe timeout or something else\r
278             out_put( "server.lua: error with new client connection: ", tostring(err) )\r
279             return false\r
280         end\r
281     end\r
282     return handler\r
283 end\r
284 \r
285 wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, startssl )    -- this function wraps a client to a handler object\r
286 \r
287     socket:settimeout( 0 )\r
288 \r
289     --// local import of socket methods //--\r
290 \r
291     local send\r
292     local receive\r
293     local shutdown\r
294 \r
295     --// private closures of the object //--\r
296 \r
297     local ssl\r
298 \r
299     local dispatch = listeners.incoming or listeners.listener\r
300     local disconnect = listeners.disconnect\r
301 \r
302     local bufferqueue = { }    -- buffer array\r
303     local bufferqueuelen = 0    -- end of buffer array\r
304 \r
305     local toclose\r
306     local fatalerror\r
307     local needtls\r
308 \r
309     local bufferlen = 0\r
310 \r
311     local noread = false\r
312     local nosend = false\r
313 \r
314     local sendtraffic, readtraffic = 0, 0\r
315 \r
316     local maxsendlen = _maxsendlen\r
317     local maxreadlen = _maxreadlen\r
318 \r
319     --// public methods of the object //--\r
320 \r
321     local handler = bufferqueue    -- saves a table ^_^\r
322 \r
323     handler.dispatch = function( )\r
324         return dispatch\r
325     end\r
326     handler.disconnect = function( )\r
327         return disconnect\r
328     end\r
329     handler.setlistener = function( listeners )\r
330         dispatch = listeners.incoming\r
331         disconnect = listeners.disconnect\r
332     end\r
333     handler.getstats = function( )\r
334         return readtraffic, sendtraffic\r
335     end\r
336     handler.ssl = function( )\r
337         return ssl\r
338     end\r
339     handler.send = function( _, data, i, j )\r
340         return send( socket, data, i, j )\r
341     end\r
342     handler.receive = function( pattern, prefix )\r
343         return receive( socket, pattern, prefix )\r
344     end\r
345     handler.shutdown = function( pattern )\r
346         return shutdown( socket, pattern )\r
347     end\r
348     handler.close = function( forced )\r
349         if not handler then return true; end\r
350         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
351         _readtimes[ handler ] = nil\r
352         if bufferqueuelen ~= 0 then\r
353             if not ( forced or fatalerror ) then\r
354                 handler.sendbuffer( )\r
355                 if bufferqueuelen ~= 0 then   -- try again...\r
356                     if handler then\r
357                         handler.write = nil    -- ... but no further writing allowed\r
358                     end\r
359                     toclose = true\r
360                     return false\r
361                 end\r
362             else\r
363                 send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send\r
364             end\r
365         end\r
366         if not handler then return true; end\r
367         _ = shutdown and shutdown( socket )\r
368         socket:close( )\r
369         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
370         _socketlist[ socket ] = nil\r
371         if handler then\r
372             _writetimes[ handler ] = nil\r
373             _closelist[ handler ] = nil\r
374             handler = nil\r
375         end\r
376         socket = nil\r
377         mem_free( )\r
378         if server then\r
379                 server.remove( )\r
380         end\r
381         out_put "server.lua: closed client handler and removed socket from list"\r
382         return true\r
383     end\r
384     handler.ip = function( )\r
385         return ip\r
386     end\r
387     handler.serverport = function( )\r
388         return serverport\r
389     end\r
390     handler.clientport = function( )\r
391         return clientport\r
392     end\r
393     local write = function( data )\r
394         bufferlen = bufferlen + string_len( data )\r
395         if bufferlen > maxsendlen then\r
396             _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle\r
397             handler.write = idfalse    -- dont write anymore\r
398             return false\r
399         elseif socket and not _sendlist[ socket ] then\r
400             _sendlistlen = _sendlistlen + 1\r
401             _sendlist[ _sendlistlen ] = socket\r
402             _sendlist[ socket ] = _sendlistlen\r
403         end\r
404         bufferqueuelen = bufferqueuelen + 1\r
405         bufferqueue[ bufferqueuelen ] = data\r
406         if handler then\r
407                 _writetimes[ handler ] = _writetimes[ handler ] or _currenttime\r
408         end\r
409         return true\r
410     end\r
411     handler.write = write\r
412     handler.bufferqueue = function( )\r
413         return bufferqueue\r
414     end\r
415     handler.socket = function( )\r
416         return socket\r
417     end\r
418     handler.pattern = function( new )\r
419         pattern = new or pattern\r
420         return pattern\r
421     end\r
422     handler.setsend = function ( newsend )\r
423         send = newsend or send\r
424         return send\r
425     end\r
426     handler.bufferlen = function( readlen, sendlen )\r
427         maxsendlen = sendlen or maxsendlen\r
428         maxreadlen = readlen or maxreadlen\r
429         return maxreadlen, maxsendlen\r
430     end\r
431     handler.lock = function( switch )\r
432         if switch == true then\r
433             handler.write = idfalse\r
434             local tmp = _sendlistlen\r
435             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
436             _writetimes[ handler ] = nil\r
437             if _sendlistlen ~= tmp then\r
438                 nosend = true\r
439             end\r
440             tmp = _readlistlen\r
441             _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
442             _readtimes[ handler ] = nil\r
443             if _readlistlen ~= tmp then\r
444                 noread = true\r
445             end\r
446         elseif switch == false then\r
447             handler.write = write\r
448             if noread then\r
449                 noread = false\r
450                 _readlistlen = _readlistlen + 1\r
451                 _readlist[ socket ] = _readlistlen\r
452                 _readlist[ _readlistlen ] = socket\r
453                 _readtimes[ handler ] = _currenttime\r
454             end\r
455             if nosend then\r
456                 nosend = false\r
457                 write( "" )\r
458             end\r
459         end\r
460         return noread, nosend\r
461     end\r
462     local _readbuffer = function( )    -- this function reads data\r
463         local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"\r
464         if not err or ( err == "timeout" or err == "wantread" ) then    -- received something\r
465             local buffer = buffer or part or ""\r
466             local len = string_len( buffer )\r
467             if len > maxreadlen then\r
468                 disconnect( handler, "receive buffer exceeded" )\r
469                 handler.close( true )\r
470                 return false\r
471             end\r
472             local count = len * STAT_UNIT\r
473             readtraffic = readtraffic + count\r
474             _readtraffic = _readtraffic + count\r
475             _readtimes[ handler ] = _currenttime\r
476             --out_put( "server.lua: read data '", buffer, "', error: ", err )\r
477             return dispatch( handler, buffer, err )\r
478         else    -- connections was closed or fatal error\r
479             out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )\r
480             fatalerror = true\r
481             disconnect( handler, err )\r
482             _ = handler and handler.close( )\r
483             return false\r
484         end\r
485     end\r
486     local _sendbuffer = function( )    -- this function sends data\r
487         local succ, err, byte, buffer, count;\r
488         local count;\r
489         if socket then\r
490             buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )\r
491             succ, err, byte = send( socket, buffer, 1, bufferlen )\r
492             count = ( succ or byte or 0 ) * STAT_UNIT\r
493             sendtraffic = sendtraffic + count\r
494             _sendtraffic = _sendtraffic + count\r
495             _ = _cleanqueue and clean( bufferqueue )\r
496             --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )\r
497         else\r
498             succ, err, count = false, "closed", 0;\r
499         end\r
500         if succ then    -- sending succesful\r
501             bufferqueuelen = 0\r
502             bufferlen = 0\r
503             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist\r
504             _ = needtls and handler.starttls(true)\r
505             _writetimes[ handler ] = nil\r
506             _ = toclose and handler.close( )\r
507             return true\r
508         elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write\r
509             buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer\r
510             bufferqueue[ 1 ] = buffer    -- insert new buffer in queue\r
511             bufferqueuelen = 1\r
512             bufferlen = bufferlen - byte\r
513             _writetimes[ handler ] = _currenttime\r
514             return true\r
515         else    -- connection was closed during sending or fatal error\r
516             out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )\r
517             fatalerror = true\r
518             disconnect( handler, err )\r
519             _ = handler and handler.close( )\r
520             return false\r
521         end\r
522     end\r
523 \r
524     if sslctx then    -- ssl?\r
525         ssl = true\r
526         local wrote\r
527         local read\r
528         local handshake = coroutine_wrap( function( client )    -- create handshake coroutine\r
529                 local err\r
530                 for i = 1, 10 do    -- 10 handshake attemps\r
531                     _sendlistlen = ( wrote and removesocket( _sendlist, socket, _sendlistlen ) ) or _sendlistlen\r
532                     _readlistlen = ( read and removesocket( _readlist, socket, _readlistlen ) ) or _readlistlen\r
533                     read, wrote = nil, nil\r
534                     _, err = client:dohandshake( )\r
535                     if not err then\r
536                         out_put( "server.lua: ssl handshake done" )\r
537                         handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions\r
538                         handler.sendbuffer = _sendbuffer\r
539                         -- return dispatch( handler )\r
540                         return true\r
541                     else\r
542                         out_put( "server.lua: error during ssl handshake: ", tostring(err) )\r
543                         if err == "wantwrite" and not wrote then\r
544                             _sendlistlen = _sendlistlen + 1\r
545                             _sendlist[ _sendlistlen ] = client\r
546                             wrote = true\r
547                         elseif err == "wantread" and not read then\r
548                                 _readlistlen = _readlistlen + 1\r
549                                 _readlist [ _readlistlen ] = client\r
550                                 read = true\r
551                         else\r
552                                 break;\r
553                         end\r
554                         --coroutine_yield( handler, nil, err )    -- handshake not finished\r
555                         coroutine_yield( )\r
556                     end\r
557                 end\r
558                 disconnect( handler, "ssl handshake failed" )\r
559                 _ = handler and handler.close( true )    -- forced disconnect\r
560                 return false    -- handshake failed\r
561             end\r
562         )\r
563         if startssl then    -- ssl now?\r
564             --out_put("server.lua: ", "starting ssl handshake")\r
565             local err\r
566             socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
567             if err then\r
568                 out_put( "server.lua: ssl error: ", tostring(err) )\r
569                 mem_free( )\r
570                 return nil, nil, err    -- fatal error\r
571             end\r
572             socket:settimeout( 0 )\r
573             handler.readbuffer = handshake\r
574             handler.sendbuffer = handshake\r
575             handshake( socket ) -- do handshake\r
576             if not socket then\r
577                 return nil, nil, "ssl handshake failed";\r
578             end\r
579         else\r
580             -- We're not automatically doing SSL, so we're not secure (yet)\r
581             ssl = false\r
582             handler.starttls = function( now )\r
583                 if not now then\r
584                     --out_put "server.lua: we need to do tls, but delaying until later"\r
585                     needtls = true\r
586                     return\r
587                 end\r
588                 --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
589                 local oldsocket, err = socket\r
590                 socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
591                 --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
592                 if err then\r
593                     out_put( "server.lua: error while starting tls on client: ", tostring(err) )\r
594                     return nil, err    -- fatal error\r
595                 end\r
596 \r
597                 socket:settimeout( 0 )\r
598 \r
599                 -- add the new socket to our system\r
600 \r
601                 send = socket.send\r
602                 receive = socket.receive\r
603                 shutdown = id\r
604 \r
605                 _socketlist[ socket ] = handler\r
606                 _readlistlen = _readlistlen + 1\r
607                 _readlist[ _readlistlen ] = socket\r
608                 _readlist[ socket ] = _readlistlen\r
609 \r
610                 -- remove traces of the old socket\r
611 \r
612                 _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
613                 _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
614                 _socketlist[ oldsocket ] = nil\r
615 \r
616                 handler.starttls = nil\r
617                 needtls = nil\r
618                 \r
619                 -- Secure now\r
620                 ssl = true\r
621 \r
622                 handler.readbuffer = handshake\r
623                 handler.sendbuffer = handshake\r
624                 handshake( socket )    -- do handshake\r
625             end\r
626             handler.readbuffer = _readbuffer\r
627             handler.sendbuffer = _sendbuffer\r
628         end\r
629     else    -- normal connection\r
630         ssl = false\r
631         handler.readbuffer = _readbuffer\r
632         handler.sendbuffer = _sendbuffer\r
633     end\r
634 \r
635     send = socket.send\r
636     receive = socket.receive\r
637     shutdown = ( ssl and id ) or socket.shutdown\r
638 \r
639     _socketlist[ socket ] = handler\r
640     _readlistlen = _readlistlen + 1\r
641     _readlist[ _readlistlen ] = socket\r
642     _readlist[ socket ] = _readlistlen\r
643 \r
644     return handler, socket\r
645 end\r
646 \r
647 id = function( )\r
648 end\r
649 \r
650 idfalse = function( )\r
651     return false\r
652 end\r
653 \r
654 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
655     local pos = list[ socket ]\r
656     if pos then\r
657         list[ socket ] = nil\r
658         local last = list[ len ]\r
659         list[ len ] = nil\r
660         if last ~= socket then\r
661             list[ last ] = pos\r
662             list[ pos ] = last\r
663         end\r
664         return len - 1\r
665     end\r
666     return len\r
667 end\r
668 \r
669 closesocket = function( socket )\r
670     _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
671     _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
672     _socketlist[ socket ] = nil\r
673     socket:close( )\r
674     mem_free( )\r
675 end\r
676 \r
677 ----------------------------------// PUBLIC //--\r
678 \r
679 addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
680     local err\r
681     --out_put("server.lua: autossl on ", port, " is ", startssl)\r
682     if type( listeners ) ~= "table" then\r
683         err = "invalid listener table"\r
684     end\r
685     if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
686         err = "invalid port"\r
687     elseif _server[ port ] then\r
688         err =  "listeners on port '" .. port .. "' already exist"\r
689     elseif sslctx and not luasec then\r
690         err = "luasec not found"\r
691     end\r
692     if err then\r
693         out_error( "server.lua, port ", port, ": ", err )\r
694         return nil, err\r
695     end\r
696     addr = addr or "*"\r
697     local server, err = socket_bind( addr, port )\r
698     if err then\r
699         out_error( "server.lua, port ", port, ": ", err )\r
700         return nil, err\r
701     end\r
702     local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
703     if not handler then\r
704         server:close( )\r
705         return nil, err\r
706     end\r
707     server:settimeout( 0 )\r
708     _readlistlen = _readlistlen + 1\r
709     _readlist[ _readlistlen ] = server\r
710     _server[ port ] = handler\r
711     _socketlist[ server ] = handler\r
712     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
713     return handler\r
714 end\r
715 \r
716 getserver = function ( port )\r
717         return _server[ port ];\r
718 end\r
719 \r
720 removeserver = function( port )\r
721     local handler = _server[ port ]\r
722     if not handler then\r
723         return nil, "no server found on port '" .. tostring( port ) "'"\r
724     end\r
725     handler.close( )\r
726     _server[ port ] = nil\r
727     return true\r
728 end\r
729 \r
730 closeall = function( )\r
731     for _, handler in pairs( _socketlist ) do\r
732         handler.close( )\r
733         _socketlist[ _ ] = nil\r
734     end\r
735     _readlistlen = 0\r
736     _sendlistlen = 0\r
737     _timerlistlen = 0\r
738     _server = { }\r
739     _readlist = { }\r
740     _sendlist = { }\r
741     _timerlist = { }\r
742     _socketlist = { }\r
743     mem_free( )\r
744 end\r
745 \r
746 getsettings = function( )\r
747     return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver\r
748 end\r
749 \r
750 changesettings = function( new )\r
751     if type( new ) ~= "table" then\r
752         return nil, "invalid settings table"\r
753     end\r
754     _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
755     _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
756     _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
757     _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
758     _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
759     _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
760     _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
761     _cleanqueue = new.cleanqueue\r
762     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
763     return true\r
764 end\r
765 \r
766 addtimer = function( listener )\r
767     if type( listener ) ~= "function" then\r
768         return nil, "invalid listener function"\r
769     end\r
770     _timerlistlen = _timerlistlen + 1\r
771     _timerlist[ _timerlistlen ] = listener\r
772     return true\r
773 end\r
774 \r
775 stats = function( )\r
776     return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
777 end\r
778 \r
779 local dontstop = true; -- thinking about tomorrow, ...\r
780 \r
781 setquitting = function (quit)\r
782         dontstop = not quit;\r
783         return;\r
784 end\r
785 \r
786 loop = function( )    -- this is the main loop of the program\r
787     while dontstop do\r
788         local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
789         for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
790             local handler = _socketlist[ socket ]\r
791             if handler then\r
792                 handler.sendbuffer( )\r
793             else\r
794                 closesocket( socket )\r
795                 out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
796             end\r
797         end\r
798         for i, socket in ipairs( read ) do    -- receive data\r
799             local handler = _socketlist[ socket ]\r
800             if handler then\r
801                 handler.readbuffer( )\r
802             else\r
803                 closesocket( socket )\r
804                 out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
805             end\r
806         end\r
807         for handler, err in pairs( _closelist ) do\r
808             handler.disconnect( )( handler, err )\r
809             handler.close( true )    -- forced disconnect\r
810         end\r
811         clean( _closelist )\r
812         _currenttime = os_time( )\r
813         if os_difftime( _currenttime - _timer ) >= 1 then\r
814             for i = 1, _timerlistlen do\r
815                 _timerlist[ i ]( )    -- fire timers\r
816             end\r
817             _timer = _currenttime\r
818         end\r
819         socket_sleep( _sleeptime )    -- wait some time\r
820         --collectgarbage( )\r
821     end\r
822     return "quitting"\r
823 end\r
824 \r
825 --// EXPERIMENTAL //--\r
826 \r
827 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
828     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
829     _socketlist[ socket ] = handler\r
830     _sendlistlen = _sendlistlen + 1\r
831     _sendlist[ _sendlistlen ] = socket\r
832     _sendlist[ socket ] = _sendlistlen\r
833     return handler, socket\r
834 end\r
835 \r
836 local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
837     local client, err = luasocket.tcp( )\r
838     if err then\r
839         return nil, err\r
840     end\r
841     client:settimeout( 0 )\r
842     _, err = client:connect( address, port )\r
843     if err then    -- try again\r
844         local handler = wrapclient( client, address, port, listeners )\r
845     else\r
846         wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )\r
847     end\r
848 end\r
849 \r
850 --// EXPERIMENTAL //--\r
851 \r
852 ----------------------------------// BEGIN //--\r
853 \r
854 use "setmetatable" ( _socketlist, { __mode = "k" } )\r
855 use "setmetatable" ( _readtimes, { __mode = "k" } )\r
856 use "setmetatable" ( _writetimes, { __mode = "k" } )\r
857 \r
858 _timer = os_time( )\r
859 _starttime = os_time( )\r
860 \r
861 addtimer( function( )\r
862         local difftime = os_difftime( _currenttime - _starttime )\r
863         if difftime > _checkinterval then\r
864             _starttime = _currenttime\r
865             for handler, timestamp in pairs( _writetimes ) do\r
866                 if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
867                     --_writetimes[ handler ] = nil\r
868                     handler.disconnect( )( handler, "send timeout" )\r
869                     handler.close( true )    -- forced disconnect\r
870                 end\r
871             end\r
872             for handler, timestamp in pairs( _readtimes ) do\r
873                 if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
874                     --_readtimes[ handler ] = nil\r
875                     handler.disconnect( )( handler, "read timeout" )\r
876                     handler.close( )    -- forced disconnect?\r
877                 end\r
878             end\r
879         end\r
880     end\r
881 )\r
882 \r
883 ----------------------------------// PUBLIC INTERFACE //--\r
884 \r
885 return {\r
886 \r
887     addclient = addclient,\r
888     wrapclient = wrapclient,\r
889     \r
890     loop = loop,\r
891     stats = stats,\r
892     closeall = closeall,\r
893     addtimer = addtimer,\r
894     addserver = addserver,\r
895     getserver = getserver,\r
896     getsettings = getsettings,\r
897     setquitting = setquitting,\r
898     removeserver = removeserver,\r
899     changesettings = changesettings,\r
900 }\r