Merge 0.9->0.10
authorKim Alvefur <zash@zash.se>
Tue, 19 Apr 2016 15:30:30 +0000 (17:30 +0200)
committerKim Alvefur <zash@zash.se>
Tue, 19 Apr 2016 15:30:30 +0000 (17:30 +0200)
1  2 
net/server_event.lua
plugins/muc/muc.lib.lua

diff --combined net/server_event.lua
index 2edc9a0c30c5c6dbecb980f436f43ad2919172bf,1c6f154789d88b08011543cbb9c99bbdf96b2cc9..a67d5afb83dd22371fd6fb95939a426c956bb30a
@@@ -11,7 -11,6 +11,7 @@@
                        -- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing
  
  --]]
 +-- luacheck: ignore 212/self 431/err 211/ret
  
  local SCRIPT_NAME           = "server_event.lua"
  local SCRIPT_VERSION        = "0.05"
@@@ -33,32 -32,27 +33,32 @@@ local cfg = 
        DEBUG                 = true,  -- show debug messages
  }
  
 -local function use(x) return rawget(_G, x); end
 -local ipairs = use "ipairs"
 -local string = use "string"
 -local select = use "select"
 -local require = use "require"
 -local tostring = use "tostring"
 -local coroutine = use "coroutine"
 -local setmetatable = use "setmetatable"
 +local pairs = pairs
 +local select = select
 +local require = require
 +local tostring = tostring
 +local setmetatable = setmetatable
  
  local t_insert = table.insert
  local t_concat = table.concat
 +local s_sub = string.sub
  
 -local ssl = use "ssl"
 -local socket = use "socket" or require "socket"
 +local coroutine_wrap = coroutine.wrap
 +local coroutine_yield = coroutine.yield
 +
 +local has_luasec, ssl = pcall ( require , "ssl" )
 +local socket = require "socket"
 +local levent = require "luaevent.core"
 +
 +local socket_gettime = socket.gettime
 +local getaddrinfo = socket.dns.getaddrinfo
  
  local log = require ("util.logger").init("socket")
  
  local function debug(...)
        return log("debug", ("%s "):rep(select('#', ...)), ...)
  end
 -local vdebug = debug;
 +-- local vdebug = debug;
  
  local bitor = ( function( ) -- thx Rici Lake
        local hasbit = function( x, p )
        end
  end )( )
  
 -local event = require "luaevent.core"
 -local base = event.new( )
 -local EV_READ = event.EV_READ
 -local EV_WRITE = event.EV_WRITE
 -local EV_TIMEOUT = event.EV_TIMEOUT
 -local EV_SIGNAL = event.EV_SIGNAL
 +local base = levent.new( )
 +local addevent = base.addevent
 +local EV_READ = levent.EV_READ
 +local EV_WRITE = levent.EV_WRITE
 +local EV_TIMEOUT = levent.EV_TIMEOUT
 +local EV_SIGNAL = levent.EV_SIGNAL
  
  local EV_READWRITE = bitor( EV_READ, EV_WRITE )
  
 -local interfacelist = ( function( )  -- holds the interfaces for sockets
 -      local array = { }
 -      local len = 0
 -      return function( method, arg )
 -              if "add" == method then
 -                      len = len + 1
 -                      array[ len ] = arg
 -                      arg:_position( len )
 -                      return len
 -              elseif "delete" == method then
 -                      if len <= 0 then
 -                              return nil, "array is already empty"
 +local interfacelist = { }
 +
 +-- Client interface methods
 +local interface_mt = {}; interface_mt.__index = interface_mt;
 +
 +-- Private methods
 +function interface_mt:_close()
 +      return self:_destroy();
 +end
 +
 +function interface_mt:_start_connection(plainssl) -- called from wrapclient
 +      local callback = function( event )
 +              if EV_TIMEOUT == event then  -- timeout during connection
 +                      self.fatalerror = "connection timeout"
 +                      self:ontimeout()  -- call timeout listener
 +                      self:_close()
 +                      debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
 +              else
 +                      if plainssl and has_luasec then  -- start ssl session
 +                              self:starttls(self._sslctx, true)
 +                      else  -- normal connection
 +                              self:_start_session(true)
                        end
 -                      local position = arg:_position()  -- get position in array
 -                      if position ~= len then
 -                              local interface = array[ len ]  -- get last interface
 -                              array[ position ] = interface  -- copy it into free position
 -                              array[ len ] = nil  -- free last position
 -                              interface:_position( position )  -- set new position in array
 -                      else  -- free last position
 -                              array[ len ] = nil
 +                      debug( "new connection established. id:", self.id )
 +              end
 +              self.eventconnect = nil
 +              return -1
 +      end
 +      self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
 +      return true
 +end
 +function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
 +      if self.type == "client" then
 +              local callback = function( )
 +                      self:_lock( false,  false, false )
 +                      --vdebug( "start listening on client socket with id:", self.id )
 +                      self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
 +                      if call_onconnect then
 +                              self:onconnect()
                        end
 -                      len = len - 1
 -                      return len
 -              else
 -                      return array
 +                      self.eventsession = nil
 +                      return -1
                end
 +              self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 )
 +      else
 +              self:_lock( false )
 +              --vdebug( "start listening on server socket with id:", self.id )
 +              self.eventread = addevent( base, self.conn, EV_READ, self.readcallback )  -- register callback
        end
 -end )( )
 -
 --- Client interface methods
 -local interface_mt
 -do
 -      interface_mt = {}; interface_mt.__index = interface_mt;
 -      
 -      local addevent = base.addevent
 -      local coroutine_wrap, coroutine_yield = coroutine.wrap,coroutine.yield
 -      
 -      -- Private methods
 -      function interface_mt:_position(new_position)
 -                      self.position = new_position or self.position
 -                      return self.position;
 -      end
 -      function interface_mt:_close()
 -              return self:_destroy();
 -      end
 -      
 -      function interface_mt:_start_connection(plainssl) -- should be called from addclient
 -                      local callback = function( event )
 -                              if EV_TIMEOUT == event then  -- timeout during connection
 -                                      self.fatalerror = "connection timeout"
 -                                      self:ontimeout()  -- call timeout listener
 -                                      self:_close()
 -                                      debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
 -                              else
 -                                      if plainssl and ssl then  -- start ssl session
 -                                              self:starttls(self._sslctx, true)
 -                                      else  -- normal connection
 -                                              self:_start_session(true)
 +      return true
 +end
 +function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first
 +      --vdebug( "starting ssl session with client id:", self.id )
 +      local _
 +      _ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
 +      _ = self.eventwrite and self.eventwrite:close( )
 +      self.eventread, self.eventwrite = nil, nil
 +      local err
 +      self.conn, err = ssl.wrap( self.conn, self._sslctx )
 +      if err then
 +              self.fatalerror = err
 +              self.conn = nil  -- cannot be used anymore
 +              if call_onconnect then
 +                      self.ondisconnect = nil  -- dont call this when client isnt really connected
 +              end
 +              self:_close()
 +              debug( "fatal error while ssl wrapping:", err )
 +              return false
 +      end
 +      self.conn:settimeout( 0 )  -- set non blocking
 +      local handshakecallback = coroutine_wrap(function( event )
 +              local _, err
 +              local attempt = 0
 +              local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS
 +              while attempt < maxattempt do  -- no endless loop
 +                      attempt = attempt + 1
 +                      debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt )
 +                      if attempt > maxattempt then
 +                              self.fatalerror = "max handshake attempts exceeded"
 +                      elseif EV_TIMEOUT == event then
 +                              self.fatalerror = "timeout during handshake"
 +                      else
 +                              _, err = self.conn:dohandshake( )
 +                              if not err then
 +                                      self:_lock( false, false, false )  -- unlock the interface; sending, closing etc allowed
 +                                      self.send = self.conn.send  -- caching table lookups with new client object
 +                                      self.receive = self.conn.receive
 +                                      if not call_onconnect then  -- trigger listener
 +                                              self:onstatus("ssl-handshake-complete");
                                        end
 -                                      debug( "new connection established. id:", self.id )
 +                                      self:_start_session( call_onconnect )
 +                                      debug( "ssl handshake done" )
 +                                      self.eventhandshake = nil
 +                                      return -1
                                end
 -                              self.eventconnect = nil
 -                              return -1
 -                      end
 -                      self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
 -                      return true
 -      end
 -      function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
 -              if self.type == "client" then
 -                      local callback = function( )
 -                              self:_lock( false,  false, false )
 -                              --vdebug( "start listening on client socket with id:", self.id )
 -                              self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
 -                              if call_onconnect then
 -                                      self:onconnect()
 +                              if err == "wantwrite" then
 +                                      event = EV_WRITE
 +                              elseif err == "wantread" then
 +                                      event = EV_READ
 +                              else
 +                                      debug( "ssl handshake error:", err )
 +                                      self.fatalerror = err
                                end
 -                              self.eventsession = nil
 -                              return -1
                        end
 -                      self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 )
 -              else
 -                      self:_lock( false )
 -                      --vdebug( "start listening on server socket with id:", self.id )
 -                      self.eventread = addevent( base, self.conn, EV_READ, self.readcallback )  -- register callback
 -              end
 -              return true
 -      end
 -      function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first
 -                      --vdebug( "starting ssl session with client id:", self.id )
 -                      local _
 -                      _ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
 -                      _ = self.eventwrite and self.eventwrite:close( )
 -                      self.eventread, self.eventwrite = nil, nil
 -                      local err
 -                      self.conn, err = ssl.wrap( self.conn, self._sslctx )
 -                      if err then
 -                              self.fatalerror = err
 -                              self.conn = nil  -- cannot be used anymore
 +                      if self.fatalerror then
                                if call_onconnect then
                                        self.ondisconnect = nil  -- dont call this when client isnt really connected
                                end
                                self:_close()
 -                              debug( "fatal error while ssl wrapping:", err )
 -                              return false
 -                      end
 -                      self.conn:settimeout( 0 )  -- set non blocking
 -                      local handshakecallback = coroutine_wrap(
 -                              function( event )
 -                                      local _, err
 -                                      local attempt = 0
 -                                      local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS
 -                                      while attempt < maxattempt do  -- no endless loop
 -                                              attempt = attempt + 1
 -                                              debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt )
 -                                              if attempt > maxattempt then
 -                                                      self.fatalerror = "max handshake attempts exceeded"
 -                                              elseif EV_TIMEOUT == event then
 -                                                      self.fatalerror = "timeout during handshake"
 -                                              else
 -                                                      _, err = self.conn:dohandshake( )
 -                                                      if not err then
 -                                                              self:_lock( false, false, false )  -- unlock the interface; sending, closing etc allowed
 -                                                              self.send = self.conn.send  -- caching table lookups with new client object
 -                                                              self.receive = self.conn.receive
 -                                                              if not call_onconnect then  -- trigger listener
 -                                                                      self:onstatus("ssl-handshake-complete");
 -                                                              end
 -                                                              self:_start_session( call_onconnect )
 -                                                              debug( "ssl handshake done" )
 -                                                              self.eventhandshake = nil
 -                                                              return -1
 -                                                      end
 -                                                      if err == "wantwrite" then
 -                                                              event = EV_WRITE
 -                                                      elseif err == "wantread" then
 -                                                              event = EV_READ
 -                                                      else
 -                                                              debug( "ssl handshake error:", err )
 -                                                              self.fatalerror = err
 -                                                      end
 -                                              end
 -                                              if self.fatalerror then
 -                                                      if call_onconnect then
 -                                                              self.ondisconnect = nil  -- dont call this when client isnt really connected
 -                                                      end
 -                                                      self:_close()
 -                                                      debug( "handshake failed because:", self.fatalerror )
 -                                                      self.eventhandshake = nil
 -                                                      return -1
 -                                              end
 -                                              event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT )  -- yield this monster...
 -                                      end
 -                              end
 -                      )
 -                      debug "starting handshake..."
 -                      self:_lock( false, true, true )  -- unlock read/write events, but keep interface locked
 -                      self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT )
 -                      return true
 -      end
 -      function interface_mt:_destroy()  -- close this interface + events and call last listener
 -                      debug( "closing client with id:", self.id, self.fatalerror )
 -                      self:_lock( true, true, true )  -- first of all, lock the interface to avoid further actions
 -                      local _
 -                      _ = self.eventread and self.eventread:close( )
 -                      if self.type == "client" then
 -                              _ = self.eventwrite and self.eventwrite:close( )
 -                              _ = self.eventhandshake and self.eventhandshake:close( )
 -                              _ = self.eventstarthandshake and self.eventstarthandshake:close( )
 -                              _ = self.eventconnect and self.eventconnect:close( )
 -                              _ = self.eventsession and self.eventsession:close( )
 -                              _ = self.eventwritetimeout and self.eventwritetimeout:close( )
 -                              _ = self.eventreadtimeout and self.eventreadtimeout:close( )
 -                              _ = self.ondisconnect and self:ondisconnect( self.fatalerror ~= "client to close" and self.fatalerror)  -- call ondisconnect listener (wont be the case if handshake failed on connect)
 -                              _ = self.conn and self.conn:close( ) -- close connection
 -                              _ = self._server and self._server:counter(-1);
 -                              self.eventread, self.eventwrite = nil, nil
 -                              self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil
 -                              self.readcallback, self.writecallback = nil, nil
 -                      else
 -                              self.conn:close( )
 -                              self.eventread, self.eventclose = nil, nil
 -                              self.interface, self.readcallback = nil, nil
 +                              debug( "handshake failed because:", self.fatalerror )
 +                              self.eventhandshake = nil
 +                              return -1
                        end
 -                      interfacelist( "delete", self )
 -                      return true
 -      end
 -      
 -      function interface_mt:_lock(nointerface, noreading, nowriting)  -- lock or unlock this interface or events
 -                      self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting
 -                      return nointerface, noreading, nowriting
 -      end
 -      
 -      --TODO: Deprecate
 -      function interface_mt:lock_read(switch)
 -              if switch then
 -                      return self:pause();
 -              else
 -                      return self:resume();
 +                      event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT )  -- yield this monster...
                end
        end
 +      )
 +      debug "starting handshake..."
 +      self:_lock( false, true, true )  -- unlock read/write events, but keep interface locked
 +      self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT )
 +      return true
 +end
 +function interface_mt:_destroy()  -- close this interface + events and call last listener
 +      debug( "closing client with id:", self.id, self.fatalerror )
 +      self:_lock( true, true, true )  -- first of all, lock the interface to avoid further actions
 +      local _
 +      _ = self.eventread and self.eventread:close( )
 +      if self.type == "client" then
 +              _ = self.eventwrite and self.eventwrite:close( )
 +              _ = self.eventhandshake and self.eventhandshake:close( )
 +              _ = self.eventstarthandshake and self.eventstarthandshake:close( )
 +              _ = self.eventconnect and self.eventconnect:close( )
 +              _ = self.eventsession and self.eventsession:close( )
 +              _ = self.eventwritetimeout and self.eventwritetimeout:close( )
 +              _ = self.eventreadtimeout and self.eventreadtimeout:close( )
 +              _ = self.ondisconnect and self:ondisconnect( self.fatalerror ~= "client to close" and self.fatalerror)  -- call ondisconnect listener (wont be the case if handshake failed on connect)
 +              _ = self.conn and self.conn:close( ) -- close connection
 +              _ = self._server and self._server:counter(-1);
 +              self.eventread, self.eventwrite = nil, nil
 +              self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil
 +              self.readcallback, self.writecallback = nil, nil
 +      else
 +              self.conn:close( )
 +              self.eventread, self.eventclose = nil, nil
 +              self.interface, self.readcallback = nil, nil
 +      end
 +      interfacelist[ self ] = nil
 +      return true
 +end
  
 -      function interface_mt:pause()
 -              return self:_lock(self.nointerface, true, self.nowriting);
 -      end
 +function interface_mt:_lock(nointerface, noreading, nowriting)  -- lock or unlock this interface or events
 +      self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting
 +      return nointerface, noreading, nowriting
 +end
  
 -      function interface_mt:resume()
 -              self:_lock(self.nointerface, false, self.nowriting);
 -              if self.readcallback and not self.eventread then
 -                      self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
 -                      return true;
 -              end
 +--TODO: Deprecate
 +function interface_mt:lock_read(switch)
 +      if switch then
 +              return self:pause();
 +      else
 +              return self:resume();
        end
 +end
  
 -      function interface_mt:counter(c)
 -              if c then
 -                      self._connections = self._connections + c
 -              end
 -              return self._connections
 -      end
 -      
 -      -- Public methods
 -      function interface_mt:write(data)
 -              if self.nowriting then return nil, "locked" end
 -              --vdebug( "try to send data to client, id/data:", self.id, data )
 -              data = tostring( data )
 -              local len = #data
 -              local total = len + self.writebufferlen
 -              if total > cfg.MAX_SEND_LENGTH then  -- check buffer length
 -                      local err = "send buffer exceeded"
 -                      debug( "error:", err )  -- to much, check your app
 -                      return nil, err
 -              end
 -              t_insert(self.writebuffer, data) -- new buffer
 -              self.writebufferlen = total
 -              if not self.eventwrite then  -- register new write event
 -                      --vdebug( "register new write event" )
 -                      self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT )
 -              end
 -              return true
 -      end
 -      function interface_mt:close()
 -              if self.nointerface then return nil, "locked"; end
 -              debug( "try to close client connection with id:", self.id )
 -              if self.type == "client" then
 -                      self.fatalerror = "client to close"
 -                      if self.eventwrite then -- wait for incomplete write request
 -                              self:_lock( true, true, false )
 -                              debug "closing delayed until writebuffer is empty"
 -                              return nil, "writebuffer not empty, waiting"
 -                      else -- close now
 -                              self:_lock( true, true, true )
 -                              self:_close()
 -                              return true
 -                      end
 -              else
 -                      debug( "try to close server with id:", tostring(self.id))
 -                      self.fatalerror = "server to close"
 -                      self:_lock( true )
 -                      self:_close( 0 )
 -                      return true
 -              end
 -      end
 -      
 -      function interface_mt:socket()
 -              return self.conn
 -      end
 -      
 -      function interface_mt:server()
 -              return self._server or self;
 -      end
 -      
 -      function interface_mt:port()
 -              return self._port
 -      end
 -      
 -      function interface_mt:serverport()
 -              return self._serverport
 -      end
 -      
 -      function interface_mt:ip()
 -              return self._ip
 -      end
 -      
 -      function interface_mt:ssl()
 -              return self._usingssl
 -      end
 -      interface_mt.clientport = interface_mt.port -- COMPAT server_select
 +function interface_mt:pause()
 +      return self:_lock(self.nointerface, true, self.nowriting);
 +end
  
 -      function interface_mt:type()
 -              return self._type or "client"
 -      end
 -      
 -      function interface_mt:connections()
 -              return self._connections
 +function interface_mt:resume()
 +      self:_lock(self.nointerface, false, self.nowriting);
 +      if self.readcallback and not self.eventread then
 +              self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
 +              return true;
        end
 -      
 -      function interface_mt:address()
 -              return self.addr
 -      end
 -      
 -      function interface_mt:set_sslctx(sslctx)
 -              self._sslctx = sslctx;
 -              if sslctx then
 -                      self.starttls = nil; -- use starttls() of interface_mt
 -              else
 -                      self.starttls = false; -- prevent starttls()
 -              end
 +end
 +
 +function interface_mt:counter(c)
 +      if c then
 +              self._connections = self._connections + c
        end
 +      return self._connections
 +end
  
 -      function interface_mt:set_mode(pattern)
 -              if pattern then
 -                      self._pattern = pattern;
 -              end
 -              return self._pattern;
 -      end
 -      
 -      function interface_mt:set_send(new_send)
 -              -- No-op, we always use the underlying connection's send
 -      end
 -      
 -      function interface_mt:starttls(sslctx, call_onconnect)
 -              debug( "try to start ssl at client id:", self.id )
 -              local err
 -              self._sslctx = sslctx;
 -              if self._usingssl then  -- startssl was already called
 -                      err = "ssl already active"
 -              end
 -              if err then
 -                      debug( "error:", err )
 -                      return nil, err
 -              end
 -              self._usingssl = true
 -              self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event
 -                      self.startsslcallback = nil
 -                      self:_start_ssl(call_onconnect);
 -                      self.eventstarthandshake = nil
 -                      return -1
 -              end
 -              if not self.eventwrite then
 -                      self:_lock( true, true, true )  -- lock the interface, to not disturb the handshake
 -                      self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 )  -- add event to start handshake
 -              else  -- wait until writebuffer is empty
 +-- Public methods
 +function interface_mt:write(data)
 +      if self.nowriting then return nil, "locked" end
 +      --vdebug( "try to send data to client, id/data:", self.id, data )
 +      data = tostring( data )
 +      local len = #data
 +      local total = len + self.writebufferlen
 +      if total > cfg.MAX_SEND_LENGTH then  -- check buffer length
 +              local err = "send buffer exceeded"
 +              debug( "error:", err )  -- to much, check your app
 +              return nil, err
 +      end
 +      t_insert(self.writebuffer, data) -- new buffer
 +      self.writebufferlen = total
 +      if not self.eventwrite then  -- register new write event
 +              --vdebug( "register new write event" )
 +              self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT )
 +      end
 +      return true
 +end
 +function interface_mt:close()
 +      if self.nointerface then return nil, "locked"; end
 +      debug( "try to close client connection with id:", self.id )
 +      if self.type == "client" then
 +              self.fatalerror = "client to close"
 +              if self.eventwrite then -- wait for incomplete write request
                        self:_lock( true, true, false )
 -                      debug "ssl session delayed until writebuffer is empty..."
 +                      debug "closing delayed until writebuffer is empty"
 +                      return nil, "writebuffer not empty, waiting"
 +              else -- close now
 +                      self:_lock( true, true, true )
 +                      self:_close()
 +                      return true
                end
 -              self.starttls = false;
 +      else
 +              debug( "try to close server with id:", tostring(self.id))
 +              self.fatalerror = "server to close"
 +              self:_lock( true )
 +              self:_close( 0 )
                return true
        end
 -      
 -      function interface_mt:setoption(option, value)
 -              if self.conn.setoption then
 -                      return self.conn:setoption(option, value);
 -              end
 -              return false, "setoption not implemented";
 -      end
 -      
 -      function interface_mt:setlistener(listener)
 -              self:ondetach(); -- Notify listener that it is no longer responsible for this connection
 -              self.onconnect, self.ondisconnect, self.onincoming,
 -              self.ontimeout, self.onstatus, self.ondetach
 -                      = listener.onconnect, listener.ondisconnect, listener.onincoming,
 -                      listener.ontimeout, listener.onstatus, listener.ondetach;
 -      end
 -      
 -      -- Stub handlers
 -      function interface_mt:onconnect()
 -      end
 -      function interface_mt:onincoming()
 -      end
 -      function interface_mt:ondisconnect()
 -      end
 -      function interface_mt:ontimeout()
 -      end
 -      function interface_mt:ondrain()
 +end
 +
 +function interface_mt:socket()
 +      return self.conn
 +end
 +
 +function interface_mt:server()
 +      return self._server or self;
 +end
 +
 +function interface_mt:port()
 +      return self._port
 +end
 +
 +function interface_mt:serverport()
 +      return self._serverport
 +end
 +
 +function interface_mt:ip()
 +      return self._ip
 +end
 +
 +function interface_mt:ssl()
 +      return self._usingssl
 +end
 +interface_mt.clientport = interface_mt.port -- COMPAT server_select
 +
 +function interface_mt:type()
 +      return self._type or "client"
 +end
 +
 +function interface_mt:connections()
 +      return self._connections
 +end
 +
 +function interface_mt:address()
 +      return self.addr
 +end
 +
 +function interface_mt:set_sslctx(sslctx)
 +      self._sslctx = sslctx;
 +      if sslctx then
 +              self.starttls = nil; -- use starttls() of interface_mt
 +      else
 +              self.starttls = false; -- prevent starttls()
        end
 -      function interface_mt:ondetach()
 +end
 +
 +function interface_mt:set_mode(pattern)
 +      if pattern then
 +              self._pattern = pattern;
        end
 -      function interface_mt:onstatus()
 +      return self._pattern;
 +end
 +
 +function interface_mt:set_send(new_send) -- luacheck: ignore 212
 +      -- No-op, we always use the underlying connection's send
 +end
 +
 +function interface_mt:starttls(sslctx, call_onconnect)
 +      debug( "try to start ssl at client id:", self.id )
 +      local err
 +      self._sslctx = sslctx;
 +      if self._usingssl then  -- startssl was already called
 +              err = "ssl already active"
 +      end
 +      if err then
 +              debug( "error:", err )
 +              return nil, err
 +      end
 +      self._usingssl = true
 +      self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event
 +              self.startsslcallback = nil
 +              self:_start_ssl(call_onconnect);
 +              self.eventstarthandshake = nil
 +              return -1
 +      end
 +      if not self.eventwrite then
 +              self:_lock( true, true, true )  -- lock the interface, to not disturb the handshake
 +              self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 )  -- add event to start handshake
 +      else
 +              -- wait until writebuffer is empty
 +              self:_lock( true, true, false )
 +              debug "ssl session delayed until writebuffer is empty..."
 +      end
 +      self.starttls = false;
 +      return true
 +end
 +
 +function interface_mt:setoption(option, value)
 +      if self.conn.setoption then
 +              return self.conn:setoption(option, value);
        end
 +      return false, "setoption not implemented";
 +end
 +
 +function interface_mt:setlistener(listener)
 +      self:ondetach(); -- Notify listener that it is no longer responsible for this connection
 +      self.onconnect = listener.onconnect;
 +      self.ondisconnect = listener.ondisconnect;
 +      self.onincoming = listener.onincoming;
 +      self.ontimeout = listener.ontimeout;
 +      self.onreadtimeout = listener.onreadtimeout;
 +      self.onstatus = listener.onstatus;
 +      self.ondetach = listener.ondetach;
 +end
 +
 +-- Stub handlers
 +function interface_mt:onconnect()
 +end
 +function interface_mt:onincoming()
 +end
 +function interface_mt:ondisconnect()
 +end
 +function interface_mt:ontimeout()
 +end
 +function interface_mt:onreadtimeout()
 +      self.fatalerror = "timeout during receiving"
 +      debug( "connection failed:", self.fatalerror )
 +      self:_close()
 +      self.eventread = nil
 +end
 +function interface_mt:ondrain()
 +end
 +function interface_mt:ondetach()
 +end
 +function interface_mt:onstatus()
  end
  
  -- End of client interface methods
  
 -local handleclient;
 -do
 -      local string_sub = string.sub  -- caching table lookups
 -      local addevent = base.addevent
 -      local socket_gettime = socket.gettime
 -      function handleclient( client, ip, port, server, pattern, listener, sslctx )  -- creates an client interface
 -              --vdebug("creating client interfacce...")
 -              local interface = {
 -                      type = "client";
 -                      conn = client;
 -                      currenttime = socket_gettime( );  -- safe the origin
 -                      writebuffer = {};  -- writebuffer
 -                      writebufferlen = 0;  -- length of writebuffer
 -                      send = client.send;  -- caching table lookups
 -                      receive = client.receive;
 -                      onconnect = listener.onconnect;  -- will be called when client disconnects
 -                      ondisconnect = listener.ondisconnect;  -- will be called when client disconnects
 -                      onincoming = listener.onincoming;  -- will be called when client sends data
 -                      ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs
 -                      ondrain = listener.ondrain; -- called when writebuffer is empty
 -                      ondetach = listener.ondetach; -- called when disassociating this listener from this connection
 -                      onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS)
 -                      eventread = false, eventwrite = false, eventclose = false,
 -                      eventhandshake = false, eventstarthandshake = false;  -- event handler
 -                      eventconnect = false, eventsession = false;  -- more event handler...
 -                      eventwritetimeout = false;  -- even more event handler...
 -                      eventreadtimeout = false;
 -                      fatalerror = false;  -- error message
 -                      writecallback = false;  -- will be called on write events
 -                      readcallback = false;  -- will be called on read events
 -                      nointerface = true;  -- lock/unlock parameter of this interface
 -                      noreading = false, nowriting = false;  -- locks of the read/writecallback
 -                      startsslcallback = false;  -- starting handshake callback
 -                      position = false;  -- position of client in interfacelist
 -                      
 -                      -- Properties
 -                      _ip = ip, _port = port, _server = server, _pattern = pattern,
 -                      _serverport = (server and server:port() or nil),
 -                      _sslctx = sslctx; -- parameters
 -                      _usingssl = false;  -- client is using ssl;
 -              }
 -              if not ssl then interface.starttls = false; end
 -              interface.id = tostring(interface):match("%x+$");
 -              interface.writecallback = function( event )  -- called on write events
 -                      --vdebug( "new client write event, id/ip/port:", interface, ip, port )
 -                      if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then  -- leave this event
 -                              --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )
 -                              interface.eventwrite = false
 -                              return -1
 -                      end
 -                      if EV_TIMEOUT == event then  -- took too long to write some data to socket -> disconnect
 -                              interface.fatalerror = "timeout during writing"
 -                              debug( "writing failed:", interface.fatalerror )
 -                              interface:_close()
 -                              interface.eventwrite = false
 -                              return -1
 -                      else  -- can write :)
 -                              if interface._usingssl then  -- handle luasec
 -                                      if interface.eventreadtimeout then  -- we have to read first
 -                                              local ret = interface.readcallback( )  -- call readcallback
 -                                              --vdebug( "tried to read in writecallback, result:", ret )
 -                                      end
 -                                      if interface.eventwritetimeout then  -- luasec only
 -                                              interface.eventwritetimeout:close( )  -- first we have to close timeout event which where regged after a wantread error
 -                                              interface.eventwritetimeout = false
 -                                      end
 +local function handleclient( client, ip, port, server, pattern, listener, sslctx )  -- creates an client interface
 +      --vdebug("creating client interfacce...")
 +      local interface = {
 +              type = "client";
 +              conn = client;
 +              currenttime = socket_gettime( );  -- safe the origin
 +              writebuffer = {};  -- writebuffer
 +              writebufferlen = 0;  -- length of writebuffer
 +              send = client.send;  -- caching table lookups
 +              receive = client.receive;
 +              onconnect = listener.onconnect;  -- will be called when client disconnects
 +              ondisconnect = listener.ondisconnect;  -- will be called when client disconnects
 +              onincoming = listener.onincoming;  -- will be called when client sends data
 +              ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs
 +              onreadtimeout = listener.onreadtimeout; -- called when socket inactivity timeout occurs
 +              ondrain = listener.ondrain; -- called when writebuffer is empty
 +              ondetach = listener.ondetach; -- called when disassociating this listener from this connection
 +              onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS)
 +              eventread = false, eventwrite = false, eventclose = false,
 +              eventhandshake = false, eventstarthandshake = false;  -- event handler
 +              eventconnect = false, eventsession = false;  -- more event handler...
 +              eventwritetimeout = false;  -- even more event handler...
 +              eventreadtimeout = false;
 +              fatalerror = false;  -- error message
 +              writecallback = false;  -- will be called on write events
 +              readcallback = false;  -- will be called on read events
 +              nointerface = true;  -- lock/unlock parameter of this interface
 +              noreading = false, nowriting = false;  -- locks of the read/writecallback
 +              startsslcallback = false;  -- starting handshake callback
 +              position = false;  -- position of client in interfacelist
 +
 +              -- Properties
 +              _ip = ip, _port = port, _server = server, _pattern = pattern,
 +              _serverport = (server and server:port() or nil),
 +              _sslctx = sslctx; -- parameters
 +              _usingssl = false;  -- client is using ssl;
 +      }
 +      if not has_luasec then interface.starttls = false; end
 +      interface.id = tostring(interface):match("%x+$");
 +      interface.writecallback = function( event )  -- called on write events
 +              --vdebug( "new client write event, id/ip/port:", interface, ip, port )
 +              if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then  -- leave this event
 +                      --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )
 +                      interface.eventwrite = false
 +                      return -1
 +              end
 +              if EV_TIMEOUT == event then  -- took too long to write some data to socket -> disconnect
 +                      interface.fatalerror = "timeout during writing"
 +                      debug( "writing failed:", interface.fatalerror )
 +                      interface:_close()
 +                      interface.eventwrite = false
 +                      return -1
 +              else  -- can write :)
 +                      if interface._usingssl then  -- handle luasec
 +                              if interface.eventreadtimeout then  -- we have to read first
 +                                      local ret = interface.readcallback( )  -- call readcallback
 +                                      --vdebug( "tried to read in writecallback, result:", ret )
                                end
 -                              interface.writebuffer = { t_concat(interface.writebuffer) }
 -                              local succ, err, byte = interface.conn:send( interface.writebuffer[1], 1, interface.writebufferlen )
 -                              --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )
 -                              if succ then  -- writing succesful
 -                                      interface.writebuffer[1] = nil
 -                                      interface.writebufferlen = 0
 -                                      interface:ondrain();
 -                                      if interface.fatalerror then
 -                                              debug "closing client after writing"
 -                                              interface:_close()  -- close interface if needed
 -                                      elseif interface.startsslcallback then  -- start ssl connection if needed
 -                                              debug "starting ssl handshake after writing"
 -                                              interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )
 -                                      elseif interface.eventreadtimeout then
 -                                              return EV_WRITE, EV_TIMEOUT
 -                                      end
 -                                      if interface.writebuffer ~= 0 then
 -                                              -- data possibly written from ondrain
 -                                              return EV_WRITE, cfg.WRITE_TIMEOUT
 -                                      end
 -                                      interface.eventwrite = nil
 -                                      return -1
 -                              elseif byte and (err == "timeout" or err == "wantwrite") then  -- want write again
 -                                      --vdebug( "writebuffer is not empty:", err )
 -                                      interface.writebuffer[1] = string_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen )  -- new buffer
 -                                      interface.writebufferlen = interface.writebufferlen - byte
 -                                      if "wantread" == err then  -- happens only with luasec
 -                                              local callback = function( )
 -                                                      interface:_close()
 -                                                      interface.eventwritetimeout = nil
 -                                                      return -1;
 -                                              end
 -                                              interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT )  -- reg a new timeout event
 -                                              debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." )
 -                                              -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent
 -                                              return -1
 -                                      end
 -                                      return EV_WRITE, cfg.WRITE_TIMEOUT
 -                              else  -- connection was closed during writing or fatal error
 -                                      interface.fatalerror = err or "fatal error"
 -                                      debug( "connection failed in write event:", interface.fatalerror )
 -                                      interface:_close()
 -                                      interface.eventwrite = nil
 -                                      return -1
 +                              if interface.eventwritetimeout then  -- luasec only
 +                                      interface.eventwritetimeout:close( )  -- first we have to close timeout event which where regged after a wantread error
 +                                      interface.eventwritetimeout = false
                                end
                        end
 -              end
 -              
 -              interface.readcallback = function( event )  -- called on read events
 -                      --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )
 -                      if interface.noreading or interface.fatalerror then  -- leave this event
 -                              --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )
 -                              interface.eventread = nil
 -                              return -1
 -                      end
 -                      if EV_TIMEOUT == event then  -- took too long to get some data from client -> disconnect
 -                              interface.fatalerror = "timeout during receiving"
 -                              debug( "connection failed:", interface.fatalerror )
 -                              interface:_close()
 -                              interface.eventread = nil
 -                              return -1
 -                      else -- can read
 -                              if interface._usingssl then  -- handle luasec
 -                                      if interface.eventwritetimeout then  -- ok, in the past writecallback was regged
 -                                              local ret = interface.writecallback( )  -- call it
 -                                              --vdebug( "tried to write in readcallback, result:", tostring(ret) )
 -                                      end
 -                                      if interface.eventreadtimeout then
 -                                              interface.eventreadtimeout:close( )
 -                                              interface.eventreadtimeout = nil
 -                                      end
 +                      interface.writebuffer = { t_concat(interface.writebuffer) }
 +                      local succ, err, byte = interface.conn:send( interface.writebuffer[1], 1, interface.writebufferlen )
 +                      --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )
 +                      if succ then  -- writing succesful
 +                              interface.writebuffer[1] = nil
 +                              interface.writebufferlen = 0
 +                              interface:ondrain();
 +                              if interface.fatalerror then
 +                                      debug "closing client after writing"
 +                                      interface:_close()  -- close interface if needed
 +                              elseif interface.startsslcallback then  -- start ssl connection if needed
 +                                      debug "starting ssl handshake after writing"
 +                                      interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )
 +                              elseif interface.eventreadtimeout then
 +                                      return EV_WRITE, EV_TIMEOUT
                                end
 -                              local buffer, err, part = interface.conn:receive( interface._pattern )  -- receive buffer with "pattern"
 -                              --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
 -                              buffer = buffer or part
 -                              if buffer and #buffer > cfg.MAX_READ_LENGTH then  -- check buffer length
 -                                      interface.fatalerror = "receive buffer exceeded"
 -                                      debug( "fatal error:", interface.fatalerror )
 -                                      interface:_close()
 -                                      interface.eventread = nil
 -                                      return -1
++                              if interface.writebuffer ~= 0 then
++                                      -- data possibly written from ondrain
++                                      return EV_WRITE, cfg.WRITE_TIMEOUT
+                               end
 -                              if err and ( err ~= "timeout" and err ~= "wantread" ) then
 -                                      if "wantwrite" == err then -- need to read on write event
 -                                              if not interface.eventwrite then  -- register new write event if needed
 -                                                      interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
 -                                              end
 -                                              interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
 -                                                      function( )
 -                                                              interface:_close()
 -                                                      end, cfg.READ_TIMEOUT
 -                                              )
 -                                              debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." )
 -                                              -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
 -                                      else  -- connection was closed or fatal error
 -                                              interface.fatalerror = err
 -                                              debug( "connection failed in read event:", interface.fatalerror )
 +                              interface.eventwrite = nil
 +                              return -1
 +                      elseif byte and (err == "timeout" or err == "wantwrite") then  -- want write again
 +                              --vdebug( "writebuffer is not empty:", err )
 +                              interface.writebuffer[1] = s_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen )  -- new buffer
 +                              interface.writebufferlen = interface.writebufferlen - byte
 +                              if "wantread" == err then  -- happens only with luasec
 +                                      local callback = function( )
                                                interface:_close()
 -                                              interface.eventread = nil
 -                                              return -1
 +                                              interface.eventwritetimeout = nil
 +                                              return -1;
                                        end
 -                              else
 -                                      interface.onincoming( interface, buffer, err )  -- send new data to listener
 -                              end
 -                              if interface.noreading then
 -                                      interface.eventread = nil;
 -                                      return -1;
 +                                      interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT )  -- reg a new timeout event
 +                                      debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." )
 +                                      -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent
 +                                      return -1
                                end
 -                              return EV_READ, cfg.READ_TIMEOUT
 +                              return EV_WRITE, cfg.WRITE_TIMEOUT
 +                      else  -- connection was closed during writing or fatal error
 +                              interface.fatalerror = err or "fatal error"
 +                              debug( "connection failed in write event:", interface.fatalerror )
 +                              interface:_close()
 +                              interface.eventwrite = nil
 +                              return -1
                        end
                end
 +      end
  
 -              client:settimeout( 0 )  -- set non blocking
 -              setmetatable(interface, interface_mt)
 -              interfacelist( "add", interface )  -- add to interfacelist
 -              return interface
 -      end
 -end
 -
 -local handleserver
 -do
 -      function handleserver( server, addr, port, pattern, listener, sslctx )  -- creates an server interface
 -              debug "creating server interface..."
 -              local interface = {
 -                      _connections = 0;
 -                      
 -                      conn = server;
 -                      onconnect = listener.onconnect;  -- will be called when new client connected
 -                      eventread = false;  -- read event handler
 -                      eventclose = false; -- close event handler
 -                      readcallback = false; -- read event callback
 -                      fatalerror = false; -- error message
 -                      nointerface = true;  -- lock/unlock parameter
 -                      
 -                      _ip = addr, _port = port, _pattern = pattern,
 -                      _sslctx = sslctx;
 -              }
 -              interface.id = tostring(interface):match("%x+$");
 -              interface.readcallback = function( event )  -- server handler, called on incoming connections
 -                      --vdebug( "server can accept, id/addr/port:", interface, addr, port )
 -                      if interface.fatalerror then
 -                              --vdebug( "leaving this event because:", self.fatalerror )
 -                              interface.eventread = nil
 -                              return -1
 -                      end
 -                      local delay = cfg.ACCEPT_DELAY
 -                      if EV_TIMEOUT == event then
 -                              if interface._connections >= cfg.MAX_CONNECTIONS then  -- check connection count
 -                                      debug( "to many connections, seconds to wait for next accept:", delay )
 -                                      return EV_TIMEOUT, delay  -- timeout...
 -                              else
 -                                      return EV_READ  -- accept again
 -                              end
 +      interface.readcallback = function( event )  -- called on read events
 +              --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )
 +              if interface.noreading or interface.fatalerror then  -- leave this event
 +                      --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )
 +                      interface.eventread = nil
 +                      return -1
 +              end
 +              if EV_TIMEOUT == event and interface:onreadtimeout() ~= true then
 +                      return -1 -- took too long to get some data from client -> disconnect
 +              end
 +              if interface._usingssl then  -- handle luasec
 +                      if interface.eventwritetimeout then  -- ok, in the past writecallback was regged
 +                              local ret = interface.writecallback( )  -- call it
 +                              --vdebug( "tried to write in readcallback, result:", tostring(ret) )
                        end
 -                      --vdebug("max connection check ok, accepting...")
 -                      local client, err = server:accept()    -- try to accept; TODO: check err
 -                      while client do
 -                              if interface._connections >= cfg.MAX_CONNECTIONS then
 -                                      client:close( )  -- refuse connection
 -                                      debug( "maximal connections reached, refuse client connection; accept delay:", delay )
 -                                      return EV_TIMEOUT, delay  -- delay for next accept attempt
 -                              end
 -                              local client_ip, client_port = client:getpeername( )
 -                              interface._connections = interface._connections + 1  -- increase connection count
 -                              local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx )
 -                              --vdebug( "client id:", clientinterface, "startssl:", startssl )
 -                              if ssl and sslctx then
 -                                      clientinterface:starttls(sslctx, true)
 -                              else
 -                                      clientinterface:_start_session( true )
 -                              end
 -                              debug( "accepted incoming client connection from:", client_ip or "<unknown IP>", client_port or "<unknown port>", "to", port or "<unknown port>");
 -                              
 -                              client, err = server:accept()    -- try to accept again
 +                      if interface.eventreadtimeout then
 +                              interface.eventreadtimeout:close( )
 +                              interface.eventreadtimeout = nil
                        end
 -                      return EV_READ
                end
 -              
 -              server:settimeout( 0 )
 -              setmetatable(interface, interface_mt)
 -              interfacelist( "add", interface )
 -              interface:_start_session()
 -              return interface
 -      end
 -end
 -
 -local addserver = ( function( )
 -      return function( addr, port, listener, pattern, sslcfg, startssl )  -- TODO: check arguments
 -              --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil")
 -              local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket
 -              if not server then
 -                      debug( "creating server socket on "..addr.." port "..port.." failed:", err )
 -                      return nil, err
 +              local buffer, err, part = interface.conn:receive( interface._pattern )  -- receive buffer with "pattern"
 +              --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
 +              buffer = buffer or part
 +              if buffer and #buffer > cfg.MAX_READ_LENGTH then  -- check buffer length
 +                      interface.fatalerror = "receive buffer exceeded"
 +                      debug( "fatal error:", interface.fatalerror )
 +                      interface:_close()
 +                      interface.eventread = nil
 +                      return -1
                end
 -              local sslctx
 -              if sslcfg then
 -                      if not ssl then
 -                              debug "fatal error: luasec not found"
 -                              return nil, "luasec not found"
 -                      end
 -                      sslctx, err = sslcfg
 -                      if err then
 -                              debug( "error while creating new ssl context for server socket:", err )
 -                              return nil, err
 +              if err and ( err ~= "timeout" and err ~= "wantread" ) then
 +                      if "wantwrite" == err then -- need to read on write event
 +                              if not interface.eventwrite then  -- register new write event if needed
 +                                      interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
 +                              end
 +                              interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
 +                              function( )
 +                                      interface:_close()
 +                              end, cfg.READ_TIMEOUT
 +                              )
 +                              debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." )
 +                              -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
 +                      else  -- connection was closed or fatal error
 +                              interface.fatalerror = err
 +                              debug( "connection failed in read event:", interface.fatalerror )
 +                              interface:_close()
 +                              interface.eventread = nil
 +                              return -1
                        end
 +              else
 +                      interface.onincoming( interface, buffer, err )  -- send new data to listener
 +              end
 +              if interface.noreading then
 +                      interface.eventread = nil;
 +                      return -1;
                end
 -              local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- new server handler
 -              debug( "new server created with id:", tostring(interface))
 -              return interface
 +              return EV_READ, cfg.READ_TIMEOUT
        end
 -end )( )
  
 -local addclient, wrapclient
 -do
 -      function wrapclient( client, ip, port, listeners, pattern, sslctx )
 -              local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx )
 -              interface:_start_connection(sslctx)
 -              return interface, client
 -              --function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface
 -      end
 -      
 -      function addclient( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )
 -              local client, err = socket.tcp()  -- creating new socket
 -              if not client then
 -                      debug( "cannot create socket:", err )
 -                      return nil, err
 +      client:settimeout( 0 )  -- set non blocking
 +      setmetatable(interface, interface_mt)
 +      interfacelist[ interface ] = true  -- add to interfacelist
 +      return interface
 +end
 +
 +local function handleserver( server, addr, port, pattern, listener, sslctx )  -- creates an server interface
 +      debug "creating server interface..."
 +      local interface = {
 +              _connections = 0;
 +
 +              type = "server";
 +              conn = server;
 +              onconnect = listener.onconnect;  -- will be called when new client connected
 +              eventread = false;  -- read event handler
 +              eventclose = false; -- close event handler
 +              readcallback = false; -- read event callback
 +              fatalerror = false; -- error message
 +              nointerface = true;  -- lock/unlock parameter
 +
 +              _ip = addr, _port = port, _pattern = pattern,
 +              _sslctx = sslctx;
 +      }
 +      interface.id = tostring(interface):match("%x+$");
 +      interface.readcallback = function( event )  -- server handler, called on incoming connections
 +              --vdebug( "server can accept, id/addr/port:", interface, addr, port )
 +              if interface.fatalerror then
 +                      --vdebug( "leaving this event because:", self.fatalerror )
 +                      interface.eventread = nil
 +                      return -1
                end
 -              client:settimeout( 0 )  -- set nonblocking
 -              if localaddr then
 -                      local res, err = client:bind( localaddr, localport, -1 )
 -                      if not res then
 -                              debug( "cannot bind client:", err )
 -                              return nil, err
 +              local delay = cfg.ACCEPT_DELAY
 +              if EV_TIMEOUT == event then
 +                      if interface._connections >= cfg.MAX_CONNECTIONS then  -- check connection count
 +                              debug( "to many connections, seconds to wait for next accept:", delay )
 +                              return EV_TIMEOUT, delay  -- timeout...
 +                      else
 +                              return EV_READ  -- accept again
                        end
                end
 -              local sslctx
 -              if sslcfg then  -- handle ssl/new context
 -                      if not ssl then
 -                              debug "need luasec, but not available"
 -                              return nil, "luasec not found"
 +              --vdebug("max connection check ok, accepting...")
 +              local client, err = server:accept()    -- try to accept; TODO: check err
 +              while client do
 +                      if interface._connections >= cfg.MAX_CONNECTIONS then
 +                              client:close( )  -- refuse connection
 +                              debug( "maximal connections reached, refuse client connection; accept delay:", delay )
 +                              return EV_TIMEOUT, delay  -- delay for next accept attempt
                        end
 -                      sslctx, err = sslcfg
 -                      if err then
 -                              debug( "cannot create new ssl context:", err )
 -                              return nil, err
 +                      local client_ip, client_port = client:getpeername( )
 +                      interface._connections = interface._connections + 1  -- increase connection count
 +                      local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx )
 +                      --vdebug( "client id:", clientinterface, "startssl:", startssl )
 +                      if has_luasec and sslctx then
 +                              clientinterface:starttls(sslctx, true)
 +                      else
 +                              clientinterface:_start_session( true )
                        end
 +                      debug( "accepted incoming client connection from:", client_ip or "<unknown IP>", client_port or "<unknown port>", "to", port or "<unknown port>");
 +
 +                      client, err = server:accept()    -- try to accept again
                end
 -              local res, err = client:connect( addr, serverport )  -- connect
 -              if res or ( err == "timeout" ) then
 -                      local ip, port = client:getsockname( )
 -                      local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, startssl )
 -                      interface:_start_connection( startssl )
 -                      debug( "new connection id:", interface.id )
 -                      return interface, err
 +              return EV_READ
 +      end
 +
 +      server:settimeout( 0 )
 +      setmetatable(interface, interface_mt)
 +      interfacelist[ interface ] = true
 +      interface:_start_session()
 +      return interface
 +end
 +
 +local function addserver( addr, port, listener, pattern, sslctx, startssl )  -- TODO: check arguments
 +      --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil")
 +      if sslctx and not has_luasec then
 +              debug "fatal error: luasec not found"
 +              return nil, "luasec not found"
 +      end
 +      local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket
 +      if not server then
 +              debug( "creating server socket on "..addr.." port "..port.." failed:", err )
 +              return nil, err
 +      end
 +      local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl )  -- new server handler
 +      debug( "new server created with id:", tostring(interface))
 +      return interface
 +end
 +
 +local function wrapclient( client, ip, port, listeners, pattern, sslctx )
 +      local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx )
 +      interface:_start_connection(sslctx)
 +      return interface, client
 +      --function handleclient( client, ip, port, server, pattern, listener, _, sslctx )  -- creates an client interface
 +end
 +
 +local function addclient( addr, serverport, listener, pattern, sslctx, typ )
 +      if sslctx and not has_luasec then
 +              debug "need luasec, but not available"
 +              return nil, "luasec not found"
 +      end
 +      if not typ then
 +              local addrinfo, err = getaddrinfo(addr)
 +              if not addrinfo then return nil, err end
 +              if addrinfo[1] and addrinfo[1].family == "inet6" then
 +                      typ = "tcp6"
                else
 -                      debug( "new connection failed:", err )
 -                      return nil, err
 +                      typ = "tcp"
                end
        end
 +      local create = socket[typ]
 +      if type( create ) ~= "function"  then
 +              return nil, "invalid socket type"
 +      end
 +      local client, err = create()  -- creating new socket
 +      if not client then
 +              debug( "cannot create socket:", err )
 +              return nil, err
 +      end
 +      client:settimeout( 0 )  -- set nonblocking
 +      local res, err = client:connect( addr, serverport )  -- connect
 +      if res or ( err == "timeout" ) then
 +              local ip, port = client:getsockname( )
 +              local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx )
 +              debug( "new connection id:", interface.id )
 +              return interface, err
 +      else
 +              debug( "new connection failed:", err )
 +              return nil, err
 +      end
  end
  
 -
 -local loop = function( )  -- starts the event loop
 +local function loop( )  -- starts the event loop
        base:loop( )
        return "quitting";
  end
  
 -local newevent = ( function( )
 -      local add = base.addevent
 -      return function( ... )
 -              return add( base, ... )
 -      end
 -end )( )
 +local function newevent( ... )
 +      return addevent( base, ... )
 +end
  
 -local closeallservers = function( arg )
 -      for _, item in ipairs( interfacelist( ) ) do
 +local function closeallservers ( arg )
 +      for item in pairs( interfacelist ) do
                if item.type == "server" then
                        item:close( arg )
                end
@@@ -757,9 -816,9 +761,9 @@@ en
  
  local function setquitting(yes)
        if yes then
 -               -- Quit now
 -               closeallservers();
 -               base:loopexit();
 +              -- Quit now
 +              closeallservers();
 +              base:loopexit();
        end
  end
  
@@@ -771,7 -830,7 +775,7 @@@ en
  -- being garbage-collected
  local signal_events = {}; -- [signal_num] -> event object
  local function hook_signal(signal_num, handler)
 -      local function _handler(event)
 +      local function _handler()
                local ret = handler();
                if ret ~= false then -- Continue handling this signal?
                        return EV_SIGNAL; -- Yes
@@@ -784,14 -843,14 +788,14 @@@ en
  
  local function link(sender, receiver, buffersize)
        local sender_locked;
 -      
 +
        function receiver:ondrain()
                if sender_locked then
                        sender:resume();
                        sender_locked = nil;
                end
        end
 -      
 +
        function sender:onincoming(data)
                receiver:write(data);
                if receiver.writebufferlen >= buffersize then
  end
  
  return {
 -
        cfg = cfg,
        base = base,
        loop = loop,
        link = link,
 -      event = event,
 +      event = levent,
        event_base = base,
        addevent = newevent,
        addserver = addserver,
diff --combined plugins/muc/muc.lib.lua
index 552b9e49970a5e6e5f950d6b598316d6d65008dc,f8e8f74dcfe7026dc1df765d846a9be1dbe76634..88ee43c1f4ac0ed0cafef209519169705a437b43
@@@ -1,7 -1,7 +1,7 @@@
  -- Prosody IM
  -- Copyright (C) 2008-2010 Matthew Wild
  -- Copyright (C) 2008-2010 Waqas Hussain
 --- 
 +--
  -- This project is MIT/X11 licensed. Please see the
  -- COPYING file in the source package for more information.
  --
@@@ -27,16 -27,28 +27,16 @@@ local muc_domain = nil; --module:get_ho
  local default_history_length, max_history_length = 20, math.huge;
  
  ------------
 -local function filter_xmlns_from_array(array, filters)
 -      local count = 0;
 -      for i=#array,1,-1 do
 -              local attr = array[i].attr;
 -              if filters[attr and attr.xmlns] then
 -                      t_remove(array, i);
 -                      count = count + 1;
 -              end
 -      end
 -      return count;
 -end
 -local function filter_xmlns_from_stanza(stanza, filters)
 -      if filters then
 -              if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
 -                      return stanza, filter_xmlns_from_array(stanza, filters);
 -              end
 +local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
 +local function presence_filter(tag)
 +      if presence_filters[tag.attr.xmlns] then
 +              return nil;
        end
 -      return stanza, 0;
 +      return tag;
  end
 -local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
 +
  local function get_filtered_presence(stanza)
 -      return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters);
 +      return st.clone(stanza):maptags(presence_filter);
  end
  local kickable_error_conditions = {
        ["gone"] = true;
@@@ -60,6 -72,17 +60,6 @@@ local function is_kickable_error(stanza
        local cond = get_error_condition(stanza);
        return kickable_error_conditions[cond] and cond;
  end
 -local function getUsingPath(stanza, path, getText)
 -      local tag = stanza;
 -      for _, name in ipairs(path) do
 -              if type(tag) ~= 'table' then return; end
 -              tag = tag:child_with_name(name);
 -      end
 -      if tag and getText then tag = table.concat(tag); end
 -      return tag;
 -end
 -local function getTag(stanza, path) return getUsingPath(stanza, path); end
 -local function getText(stanza, path) return getUsingPath(stanza, path, true); end
  -----------
  
  local room_mt = {};
@@@ -75,8 -98,8 +75,8 @@@ function room_mt:get_default_role(affil
        elseif affiliation == "member" then
                return "participant";
        elseif not affiliation then
 -              if not self:is_members_only() then
 -                      return self:is_moderated() and "visitor" or "participant";
 +              if not self:get_members_only() then
 +                      return self:get_moderated() and "visitor" or "participant";
                end
        end
  end
@@@ -107,21 -130,18 +107,21 @@@ function room_mt:broadcast_message(stan
        end
        stanza.attr.to = to;
        if historic then -- add to history
 -              local history = self._data['history'];
 -              if not history then history = {}; self._data['history'] = history; end
 -              stanza = st.clone(stanza);
 -              stanza.attr.to = "";
 -              local stamp = datetime.datetime();
 -              stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
 -              stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
 -              local entry = { stanza = stanza, stamp = stamp };
 -              t_insert(history, entry);
 -              while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
 +              return self:save_to_history(stanza)
        end
  end
 +function room_mt:save_to_history(stanza)
 +      local history = self._data['history'];
 +      if not history then history = {}; self._data['history'] = history; end
 +      stanza = st.clone(stanza);
 +      stanza.attr.to = "";
 +      local stamp = datetime.datetime();
 +      stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
 +      stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
 +      local entry = { stanza = stanza, stamp = stamp };
 +      t_insert(history, entry);
 +      while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
 +end
  function room_mt:broadcast_except_nick(stanza, nick)
        for rnick, occupant in pairs(self._occupants) do
                if rnick ~= nick then
@@@ -150,10 -170,10 +150,10 @@@ function room_mt:send_history(to, stanz
        if history then
                local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
                local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
 -              
 +
                local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
                if maxchars then maxchars = math.floor(maxchars); end
 -              
 +
                local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
                if not history_tag then maxstanzas = 20; end
  
  
                local n = 0;
                local charcount = 0;
 -              
 +
                for i=#history,1,-1 do
                        local entry = history[i];
                        if maxchars then
                        self:_route_stanza(msg);
                end
        end
 +end
 +function room_mt:send_subject(to)
        if self._data['subject'] then
                self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject']));
        end
@@@ -196,28 -214,21 +196,28 @@@ en
  
  function room_mt:get_disco_info(stanza)
        local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
 -      return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
 +      local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info")
                :tag("identity", {category="conference", type="text", name=self:get_name()}):up()
                :tag("feature", {var="http://jabber.org/protocol/muc"}):up()
                :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
 -              :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
 -              :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
 -              :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
 -              :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
 +              :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
 +              :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up()
 +              :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up()
 +              :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up()
                :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
 -              :add_child(dataform.new({
 -                      { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
 -                      { name = "muc#roominfo_description", label = "Description", value = "" },
 -                      { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
 -              }):form({["muc#roominfo_description"] = self:get_description()}, 'result'))
        ;
 +      local dataform = dataform.new({
 +              { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
 +              { name = "muc#roominfo_description", label = "Description", value = "" },
 +              { name = "muc#roominfo_occupants", label = "Number of occupants", value = "" }
 +      });
 +      local formdata = {
 +              ["muc#roominfo_description"] = self:get_description(),
 +              ["muc#roominfo_occupants"] = tostring(count),
 +      };
 +      module:fire_event("muc-disco#info", { room = self, reply = reply, form = dataform, formdata = formdata });
 +      reply:add_child(dataform:form(formdata, 'result'))
 +      return reply;
  end
  function room_mt:get_disco_items(stanza)
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
        return reply;
  end
  function room_mt:set_subject(current_nick, subject)
 -      -- TODO check nick's authority
        if subject == "" then subject = nil; end
        self._data['subject'] = subject;
        self._data['subject_from'] = current_nick;
@@@ -284,7 -296,7 +284,7 @@@ function room_mt:set_moderated(moderate
                if self.save then self:save(true); end
        end
  end
 -function room_mt:is_moderated()
 +function room_mt:get_moderated()
        return self._data.moderated;
  end
  function room_mt:set_members_only(members_only)
                if self.save then self:save(true); end
        end
  end
 -function room_mt:is_members_only()
 +function room_mt:get_members_only()
        return self._data.members_only;
  end
  function room_mt:set_persistent(persistent)
                if self.save then self:save(true); end
        end
  end
 -function room_mt:is_persistent()
 +function room_mt:get_persistent()
        return self._data.persistent;
  end
  function room_mt:set_hidden(hidden)
                if self.save then self:save(true); end
        end
  end
 -function room_mt:is_hidden()
 +function room_mt:get_hidden()
        return self._data.hidden;
  end
 +function room_mt:get_public()
 +      return not self:get_hidden();
 +end
 +function room_mt:set_public(public)
 +      return self:set_hidden(not public);
 +end
  function room_mt:set_changesubject(changesubject)
        changesubject = changesubject and true or nil;
        if self._data.changesubject ~= changesubject then
@@@ -345,25 -351,12 +345,25 @@@ function room_mt:set_historylength(leng
  end
  
  
 +local valid_whois = { moderators = true, anyone = true };
 +
 +function room_mt:set_whois(whois)
 +      if valid_whois[whois] and self._data.whois ~= whois then
 +              self._data.whois = whois;
 +              if self.save then self:save(true); end
 +      end
 +end
 +
 +function room_mt:get_whois()
 +      return self._data.whois;
 +end
 +
  local function construct_stanza_id(room, stanza)
        local from_jid, to_nick = stanza.attr.from, stanza.attr.to;
        local from_nick = room._jid_nick[from_jid];
        local occupant = room._occupants[to_nick];
        local to_jid = occupant.jid;
 -      
 +
        return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid));
  end
  local function deconstruct_stanza_id(room, stanza)
@@@ -492,12 -485,6 +492,12 @@@ function room_mt:handle_to_occupant(ori
                                        log("debug", "%s joining as %s", from, to);
                                        if not next(self._affiliations) then -- new room, no owners
                                                self._affiliations[jid_bare(from)] = "owner";
 +                                              if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then
 +                                                      self.locked = nil; -- Older groupchat protocol doesn't lock
 +                                              end
 +                                      elseif self.locked then -- Deny entry
 +                                              origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
 +                                              return;
                                        end
                                        local affiliation = self:get_affiliation(from);
                                        local role = self:get_default_role(affiliation)
                                                if self._data.whois == 'anyone' then
                                                        pr:tag("status", {code='100'}):up();
                                                end
 +                                              if self.locked then
 +                                                      pr:tag("status", {code='201'}):up();
 +                                              end
                                                pr.attr.to = from;
                                                self:_route_stanza(pr);
                                                self:send_history(from, stanza);
 +                                              self:send_subject(from);
                                        elseif not affiliation then -- registration required for entering members-only room
                                                local reply = st.error_reply(stanza, "auth", "registration-required"):up();
                                                reply.tags[1].attr.code = "407";
                                end
                                stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
                        else -- message
 +                              stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
                                stanza.attr.from = current_nick;
                                for jid in pairs(o_data.sessions) do
                                        stanza.attr.to = jid;
@@@ -593,11 -575,11 +593,11 @@@ en
  
  function room_mt:send_form(origin, stanza)
        origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
 -              :add_child(self:get_form_layout():form())
 +              :add_child(self:get_form_layout(stanza.attr.from):form())
        );
  end
  
 -function room_mt:get_form_layout()
 +function room_mt:get_form_layout(actor)
        local form = dataform.new({
                title = "Configuration for "..self.jid,
                instructions = "Complete and submit this form to configure the room.",
                        name = 'muc#roomconfig_persistentroom',
                        type = 'boolean',
                        label = 'Make Room Persistent?',
 -                      value = self:is_persistent()
 +                      value = self:get_persistent()
                },
                {
                        name = 'muc#roomconfig_publicroom',
                        type = 'boolean',
                        label = 'Make Room Publicly Searchable?',
 -                      value = not self:is_hidden()
 +                      value = not self:get_hidden()
                },
                {
                        name = 'muc#roomconfig_changesubject',
                        name = 'muc#roomconfig_moderatedroom',
                        type = 'boolean',
                        label = 'Make Room Moderated?',
 -                      value = self:is_moderated()
 +                      value = self:get_moderated()
                },
                {
                        name = 'muc#roomconfig_membersonly',
                        type = 'boolean',
                        label = 'Make Room Members-Only?',
 -                      value = self:is_members_only()
 +                      value = self:get_members_only()
                },
                {
                        name = 'muc#roomconfig_historylength',
                        value = tostring(self:get_historylength())
                }
        });
 -      return module:fire_event("muc-config-form", { room = self, form = form }) or form;
 +      return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form;
  end
  
 -local valid_whois = {
 -      moderators = true,
 -      anyone = true,
 -}
 -
  function room_mt:process_form(origin, stanza)
        local query = stanza.tags[1];
        local form;
        if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
        if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end
  
 -
 -      local fields = self:get_form_layout():data(form);
 -      if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end
 -
 -      local dirty = false
 -
 -      local event = { room = self, fields = fields, changed = dirty };
 -      module:fire_event("muc-config-submitted", event);
 -      dirty = event.changed or dirty;
 -
 -      local name = fields['muc#roomconfig_roomname'];
 -      if name ~= self:get_name() then
 -              self:set_name(name);
 -      end
 -
 -      local description = fields['muc#roomconfig_roomdesc'];
 -      if description ~= self:get_description() then
 -              self:set_description(description);
+       if form.tags[1] == nil then
+               -- instant room
+               if self.save then self:save(true); end
+               origin.send(st.reply(stanza));
+               return true;
+       end
 +      local fields, errors, present = self:get_form_layout(stanza.attr.from):data(form);
 +      if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then
 +              origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration"));
 +              return;
        end
  
 -      local persistent = fields['muc#roomconfig_persistentroom'];
 -      dirty = dirty or (self:is_persistent() ~= persistent)
 -      module:log("debug", "persistent=%s", tostring(persistent));
 +      local changed = {};
  
 -      local moderated = fields['muc#roomconfig_moderatedroom'];
 -      dirty = dirty or (self:is_moderated() ~= moderated)
 -      module:log("debug", "moderated=%s", tostring(moderated));
 -
 -      local membersonly = fields['muc#roomconfig_membersonly'];
 -      dirty = dirty or (self:is_members_only() ~= membersonly)
 -      module:log("debug", "membersonly=%s", tostring(membersonly));
 -
 -      local public = fields['muc#roomconfig_publicroom'];
 -      dirty = dirty or (self:is_hidden() ~= (not public and true or nil))
 -
 -      local changesubject = fields['muc#roomconfig_changesubject'];
 -      dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
 -      module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
 -
 -      local historylength = tonumber(fields['muc#roomconfig_historylength']);
 -      dirty = dirty or (historylength and (self:get_historylength() ~= historylength));
 -      module:log('debug', 'historylength=%s', historylength)
 -
 -
 -      local whois = fields['muc#roomconfig_whois'];
 -      if not valid_whois[whois] then
 -          origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
 -          return;
 +      local function handle_option(name, field, allowed)
 +              if not present[field] then return; end
 +              local new = fields[field];
 +              if allowed and not allowed[new] then return; end
 +              if new == self["get_"..name](self) then return; end
 +              changed[name] = true;
 +              self["set_"..name](self, new);
        end
 -      local whois_changed = self._data.whois ~= whois
 -      self._data.whois = whois
 -      module:log('debug', 'whois=%s', whois)
  
 -      local password = fields['muc#roomconfig_roomsecret'];
 -      if self:get_password() ~= password then
 -              self:set_password(password);
 -      end
 -      self:set_moderated(moderated);
 -      self:set_members_only(membersonly);
 -      self:set_persistent(persistent);
 -      self:set_hidden(not public);
 -      self:set_changesubject(changesubject);
 -      self:set_historylength(historylength);
 +      local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option };
 +      module:fire_event("muc-config-submitted", event);
 +
 +      handle_option("name", "muc#roomconfig_roomname");
 +      handle_option("description", "muc#roomconfig_roomdesc");
 +      handle_option("persistent", "muc#roomconfig_persistentroom");
 +      handle_option("moderated", "muc#roomconfig_moderatedroom");
 +      handle_option("members_only", "muc#roomconfig_membersonly");
 +      handle_option("public", "muc#roomconfig_publicroom");
 +      handle_option("changesubject", "muc#roomconfig_changesubject");
 +      handle_option("historylength", "muc#roomconfig_historylength");
 +      handle_option("whois", "muc#roomconfig_whois", valid_whois);
 +      handle_option("password", "muc#roomconfig_roomsecret");
  
        if self.save then self:save(true); end
 +      if self.locked then
 +              module:fire_event("muc-room-unlocked", { room = self });
 +              self.locked = nil;
 +      end
        origin.send(st.reply(stanza));
  
 -      if dirty or whois_changed then
 +      if next(changed) then
                local msg = st.message({type='groupchat', from=self.jid})
 -                      :tag('x', {xmlns='http://jabber.org/protocol/muc#user'});
 -
 -              if dirty then
 -                      msg.tags[1]:tag('status', {code = '104'}):up();
 -              end
 -              if whois_changed then
 -                      local code = (whois == 'moderators') and "173" or "172";
 +                      :tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
 +                              :tag('status', {code = '104'}):up();
 +              if changed.whois then
 +                      local code = (self:get_whois() == 'moderators') and "173" or "172";
                        msg.tags[1]:tag('status', {code = code}):up();
                end
 -              msg:up();
 -
                self:broadcast_message(msg, false)
        end
  end
@@@ -862,7 -890,7 +869,7 @@@ function room_mt:handle_to_room(origin
                        origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                end
        elseif stanza.name == "message" and type == "groupchat" then
 -              local from, to = stanza.attr.from, stanza.attr.to;
 +              local from = stanza.attr.from;
                local current_nick = self._jid_nick[from];
                local occupant = self._occupants[current_nick];
                if not occupant then -- not in room
                else
                        local from = stanza.attr.from;
                        stanza.attr.from = current_nick;
 -                      local subject = getText(stanza, {"subject"});
 +                      local subject = stanza:get_child_text("subject");
                        if subject then
                                if occupant.role == "moderator" or
                                        ( self._data.changesubject and occupant.role == "participant" ) then -- and participant
 -                                      self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
 +                                      self:set_subject(current_nick, subject);
                                else
                                        stanza.attr.from = from;
                                        origin.send(st.error_reply(stanza, "auth", "forbidden"));
                                        :tag('body') -- Add a plain message for clients which don't support invites
                                                :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
                                        :up();
 -                              if self:is_members_only() and not self:get_affiliation(_invitee) then
 +                              if self:get_members_only() and not self:get_affiliation(_invitee) then
                                        log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
                                        self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
                                end