def serve_client(session, io)
      # handle connection
      begin
        begin
          # ON CONNECTION
          # 220 <domain> Service ready
          # 421 <domain> Service not available, closing transmission channel
          # Reset and initialize message
          process_reset_session(session, true)

          # get local address info
          _, session[:ctx][:server][:local_port], session[:ctx][:server][:local_host], session[:ctx][:server][:local_ip] = @do_dns_reverse_lookup ? io.addr(:hostname) : io.addr(:numeric)
          # get remote partner hostname and address
          _, session[:ctx][:server][:remote_port], session[:ctx][:server][:remote_host], session[:ctx][:server][:remote_ip] = @do_dns_reverse_lookup ? io.peeraddr(:hostname) : io.peeraddr(:numeric)

          # save connection date/time
          session[:ctx][:server][:connected] = Time.now.utc

          # build and save the local welcome and greeting response strings
          session[:ctx][:server][:local_response] = "#{session[:ctx][:server][:local_host]} says welcome!"
          session[:ctx][:server][:helo_response] = "#{session[:ctx][:server][:local_host]} at your service!"

          # check if we want to let this remote station connect us
          on_connect_event(session[:ctx])

          # drop connection (respond 421) if too busy
          raise 'Abort connection while too busy, exceeding max_connections!' if max_connections && connections > max_connections

          # check active processings for new client
          @connections_mutex.synchronize do
            # when processings exceed maximum number of simultaneous allowed processings, then wait for next free slot
            while processings >= max_processings
              @connections_cv.wait(@connections_mutex)
            end
          end

          # append this to list of processings
          @processings << Thread.current

          # reply local welcome message
          output = "220 #{session[:ctx][:server][:local_response].to_s.strip}\r\n"

          # log and show to client
          logger.debug('>>> ' + output)
          io.print output unless io.closed?

          # initialize \r\n for line_break, this is used for CRLF_ENSURE and CRLF_STRICT
          line_break = "\r\n"

          # initialize io_buffer for input data
          io_buffer = ''

          # initialize io_buffer_line_lf index
          io_buffer_line_lf = nil

          # initialze timeout timestamp
          timestamp_timeout = Time.now.to_i

          # while input data handle communication
          loop do
            # test if STARTTLS sequence
            if session[:cmd_sequence] == :CMD_STARTTLS
              # start ssl tunnel
              io = @tls.start(io)
              # save enabled tls
              session[:ctx][:server][:encrypted] = Time.now.utc
              # set sequence back to HELO/EHLO
              session[:cmd_sequence] = :CMD_HELO
              # reset timeout timestamp
              timestamp_timeout = Time.now.to_i
            end

            # read input data from Socket / SSLSocket into io_buffer
            # by non-blocking action until \n is found
            begin
              unless io_buffer_line_lf
                # check for timeout on IO
                raise SmtpdIOTimeoutException if @io_cmd_timeout && Time.now.to_i - timestamp_timeout > @io_cmd_timeout
                # read chunks of input data until line-feed
                io_buffer << io.read_nonblock(@io_buffer_chunk_size)
                # check for buffersize
                raise SmtpdIOBufferOverrunException if @io_buffer_max_size && io_buffer.length > @io_buffer_max_size
                # check for lf in current io_buffer
                io_buffer_line_lf = io_buffer.index("\n")
              end

            # ignore exception when no input data is available yet
            rescue IO::WaitReadable
              # but wait a few moment to slow down system utilization
              sleep 0.1
            end

            # check if io_buffer is filled and contains already a line-feed
            while io_buffer_line_lf
              # extract line (containing \n) from io_buffer and slice io_buffer
              line = io_buffer.slice!(0, io_buffer_line_lf + 1)

              # check for next line-feed already in io_buffer
              io_buffer_line_lf = io_buffer.index("\n")

              # process commands and handle special SmtpdExceptions
              begin
                # check for pipelining extension or violation
                raise Smtpd500PipeliningException unless @pipelining_extension || !io_buffer_line_lf || (session[:cmd_sequence] == :CMD_DATA)

                # handle input line based on @crlf_mode
                case crlf_mode
                  when :CRLF_ENSURE
                    # remove any \r or \n occurence from line
                    line.delete!("\r\n")
                    # log line, verbosity based on log severity and command sequence
                    logger.debug('<<< ' << line << "\n") if session[:cmd_sequence] != :CMD_DATA

                  when :CRLF_LEAVE
                    # use input line_break for line_break
                    line_break = line[-2..-1] == "\r\n" ? "\r\n" : "\n"
                    # check to override session crlf info, only when CRLF_LEAVE is used and in DATA mode
                    session[:ctx][:message][:crlf] = line_break if session[:cmd_sequence] == :CMD_DATA
                    # remove any line_break from line
                    line.chomp!
                    # log line, verbosity based on log severity and command sequence
                    logger.debug('<<< ' << line.gsub("\r", '[\r]') << "\n") if session[:cmd_sequence] != :CMD_DATA

                  when :CRLF_STRICT
                    # check line ends up by \r\n
                    raise Smtpd500CrLfSequenceException unless line[-2..-1] == "\r\n"
                    # remove any line_break from line
                    line.chomp!
                    # check line for additional \r
                    raise Smtpd500Exception, 'Line contains additional CR chars!' if line.index("\r")
                    # log line, verbosity based on log severity and command sequence
                    logger.debug('<<< ' << line << "\n") if session[:cmd_sequence] != :CMD_DATA
                end

                # process line
                output = process_line(session, line, line_break)

              # defined abort channel exception
              rescue Smtpd421Exception => e
                # just re-raise this exception and exit loop and communication
                raise

              # defined SmtpdException
              rescue SmtpdException => e
                # inc number of detected exceptions during this session
                session[:ctx][:server][:exceptions] += 1
                # log error info if logging
                logger.error("#{e}")
                # get the given smtp dialog result
                output = "#{e.smtpd_result}"

              # Unknown general Exception during processing
              rescue StandardError => e
                # inc number of detected exceptions during this session
                session[:ctx][:server][:exceptions] += 1
                # log error info if logging
                logger.error("#{e}")
                # set default smtp server dialog error
                output = "#{Smtpd500Exception.new.smtpd_result}"
              end

              # check result
              unless output.empty?
                # log smtp dialog // message data is stored separate
                logger.debug('>>> ' + output)
                # append line feed
                output << "\r\n"
                # smtp dialog response
                io.print(output) unless io.closed? || shutdown?
              end

              # reset timeout timestamp
              timestamp_timeout = Time.now.to_i
            end

            # check for valid quit or broken communication
            break if (session[:cmd_sequence] == :CMD_QUIT) || io.closed? || shutdown?
          end
          # graceful end of connection
          output = "221 Service closing transmission channel\r\n"
          # smtp dialog response
          io.print(output) unless io.closed?

        # connection was simply closed / aborted by remote closing socket
        rescue EOFError
          # log info but only while debugging otherwise ignore message
          logger.debug('EOFError - Connection lost due abort by client!')

        rescue StandardError => e
          # log error info if logging
          logger.error("#{e}")
          # power down connection
          # ignore IOErrors when sending final smtp abort return code 421
          begin
            output = "#{Smtpd421Exception.new.smtpd_result}\r\n"
            # smtp dialog response
            io.print(output) unless io.closed?
          rescue StandardError
            logger.debug('IOError - Can\'t send 421 abort code!')
          end
        end

      ensure
        # event for cleanup at end of communication
        on_disconnect_event(session[:ctx])
      end

      # return socket handler, maybe replaced with ssl
      return io
    end
def serve_client(session, io)
      # handle connection
      begin
        begin
          # ON CONNECTION
          # 220 <domain> Service ready
          # 421 <domain> Service not available, closing transmission channel
          # Reset and initialize message
          process_reset_session(session, true)

          # get local address info
          _, session[:ctx][:server][:local_port], session[:ctx][:server][:local_host], session[:ctx][:server][:local_ip] = @do_dns_reverse_lookup ? io.addr(:hostname) : io.addr(:numeric)
          # get remote partner hostname and address
          _, session[:ctx][:server][:remote_port], session[:ctx][:server][:remote_host], session[:ctx][:server][:remote_ip] = @do_dns_reverse_lookup ? io.peeraddr(:hostname) : io.peeraddr(:numeric)

          # save connection date/time
          session[:ctx][:server][:connected] = Time.now.utc

          # build and save the local welcome and greeting response strings
          session[:ctx][:server][:local_response] = "#{session[:ctx][:server][:local_host]} says welcome!"
          session[:ctx][:server][:helo_response] = "#{session[:ctx][:server][:local_host]} at your service!"

          # check if we want to let this remote station connect us
          on_connect_event(session[:ctx])

          # drop connection (respond 421) if too busy
          raise 'Abort connection while too busy, exceeding max_connections!' if max_connections && connections > max_connections

          # check active processings for new client
          @connections_mutex.synchronize do
            # when processings exceed maximum number of simultaneous allowed processings, then wait for next free slot
            while processings >= max_processings
              @connections_cv.wait(@connections_mutex)
            end
          end

          # append this to list of processings
          @processings << Thread.current

          # reply local welcome message
          output = "220 #{session[:ctx][:server][:local_response].to_s.strip}\r\n"

          # log and show to client
          logger.debug('>>> ' + output)
          io.print output unless io.closed?

          # initialize \r\n for line_break, this is used for CRLF_ENSURE and CRLF_STRICT
          line_break = "\r\n"

          # initialize io_buffer for input data
          io_buffer = ''

          # initialize io_buffer_line_lf index
          io_buffer_line_lf = nil

          # initialze timeout timestamp
          timestamp_timeout = Time.now.to_i

          # while input data handle communication
          loop do
            # test if STARTTLS sequence
            if session[:cmd_sequence] == :CMD_STARTTLS
              # start ssl tunnel
              io = @tls.start(io)
              # save enabled tls
              session[:ctx][:server][:encrypted] = Time.now.utc
              # set sequence back to HELO/EHLO
              session[:cmd_sequence] = :CMD_HELO
              # reset timeout timestamp
              timestamp_timeout = Time.now.to_i
            end

            # read input data from Socket / SSLSocket into io_buffer
            # by non-blocking action until \n is found
            begin
              unless io_buffer_line_lf
                # check for timeout on IO
                raise SmtpdIOTimeoutException if @io_cmd_timeout && Time.now.to_i - timestamp_timeout > @io_cmd_timeout
                # read chunks of input data until line-feed
                io_buffer << io.read_nonblock(@io_buffer_chunk_size)
                # check for buffersize
                raise SmtpdIOBufferOverrunException if @io_buffer_max_size && io_buffer.length > @io_buffer_max_size
                # check for lf in current io_buffer
                io_buffer_line_lf = io_buffer.index("\n")
              end

            # ignore exception when no input data is available yet
            rescue IO::WaitReadable
              # but wait a few moment to slow down system utilization
              sleep 0.1
            end

            # check if io_buffer is filled and contains already a line-feed
            while io_buffer_line_lf
              # extract line (containing \n) from io_buffer and slice io_buffer
              line = io_buffer.slice!(0, io_buffer_line_lf + 1)

              # check for next line-feed already in io_buffer
              io_buffer_line_lf = io_buffer.index("\n")

              # process commands and handle special SmtpdExceptions
              begin
                # check for pipelining extension or violation
                raise Smtpd500PipeliningException unless @pipelining_extension || !io_buffer_line_lf || (session[:cmd_sequence] == :CMD_DATA)

                # handle input line based on @crlf_mode
                case crlf_mode
                  when :CRLF_ENSURE
                    # remove any \r or \n occurence from line
                    line.delete!("\r\n")
                    # log line, verbosity based on log severity and command sequence
                    logger.debug('<<< ' << line << "\n") if session[:cmd_sequence] != :CMD_DATA

                  when :CRLF_LEAVE
                    # use input line_break for line_break
                    line_break = line[-2..-1] == "\r\n" ? "\r\n" : "\n"
                    # check to override session crlf info, only when CRLF_LEAVE is used and in DATA mode
                    session[:ctx][:message][:crlf] = line_break if session[:cmd_sequence] == :CMD_DATA
                    # remove any line_break from line
                    line.chomp!
                    # log line, verbosity based on log severity and command sequence
                    logger.debug('<<< ' << line.gsub("\r", '[\r]') << "\n") if session[:cmd_sequence] != :CMD_DATA

                  when :CRLF_STRICT
                    # check line ends up by \r\n
                    raise Smtpd500CrLfSequenceException unless line[-2..-1] == "\r\n"
                    # remove any line_break from line
                    line.chomp!
                    # check line for additional \r
                    raise Smtpd500Exception, 'Line contains additional CR chars!' if line.index("\r")
                    # log line, verbosity based on log severity and command sequence
                    logger.debug('<<< ' << line << "\n") if session[:cmd_sequence] != :CMD_DATA
                end

                # process line
                output = process_line(session, line, line_break)

              # defined abort channel exception
              rescue Smtpd421Exception => e
                # just re-raise this exception and exit loop and communication
                raise

              # defined SmtpdException
              rescue SmtpdException => e
                # inc number of detected exceptions during this session
                session[:ctx][:server][:exceptions] += 1
                # log error info if logging
                logger.error("#{e}")
                # get the given smtp dialog result
                output = "#{e.smtpd_result}"

              # Unknown general Exception during processing
              rescue StandardError => e
                # inc number of detected exceptions during this session
                session[:ctx][:server][:exceptions] += 1
                # log error info if logging
                logger.error("#{e}")
                # set default smtp server dialog error
                output = "#{Smtpd500Exception.new.smtpd_result}"
              end

              # check result
              unless output.empty?
                # log smtp dialog // message data is stored separate
                logger.debug('>>> ' + output)
                # append line feed
                output << "\r\n"
                # smtp dialog response
                io.print(output) unless io.closed? || shutdown?
              end

              # reset timeout timestamp
              timestamp_timeout = Time.now.to_i
            end

            # check for valid quit or broken communication
            break if (session[:cmd_sequence] == :CMD_QUIT) || io.closed? || shutdown?
          end
          # graceful end of connection
          output = "221 Service closing transmission channel\r\n"
          # smtp dialog response
          io.print(output) unless io.closed?

        # connection was simply closed / aborted by remote closing socket
        rescue EOFError
          # log info but only while debugging otherwise ignore message
          logger.debug('EOFError - Connection lost due abort by client!')

        rescue StandardError => e
          # log error info if logging
          logger.error("#{e}")
          # power down connection
          # ignore IOErrors when sending final smtp abort return code 421
          begin
            output = "#{Smtpd421Exception.new.smtpd_result}\r\n"
            # smtp dialog response
            io.print(output) unless io.closed?
          rescue StandardError
            logger.debug('IOError - Can\'t send 421 abort code!')
          end
        end

      ensure
        # event for cleanup at end of communication
        on_disconnect_event(session[:ctx])
      end

      # return socket handler, maybe replaced with ssl
      return io
    end
handle connection