class ImapServer # Handle one client socket class ClientHandler Dir["lib/imap_server/client_handler/*.rb"].each do |mod| mod_name = File.basename(mod, ".rb") require_relative "client_handler/#{mod_name}" include const_get(mod_name.capitalize) end def initialize(server:, socket:) @server = server @socket = socket end # write something to the client def write(*messages) if messages.size == 1 raw_message = messages.first.to_s $logger.debug "S: #{raw_message.chomp}" @socket.write raw_message else messages.each { write(_1) } end end # read the socket and return the next client message def read input = @socket.gets if !@socket.closed? input&.chomp! $logger.debug "C: #{input}" input end def greeting write ServerMessage.new("OK IMAP4rev1 AUTH=PLAIN server ready") end # Start a new imap connection def start greeting loop do client_input = read break if client_input.nil? process(client_input) end end # Handles ONE input from the client. # Handlement is done by delegating behavior of the command to external handler # loaded as modules. # # The handlers start as #handle_ followed with the command token downcased. # Handlers will receive the parser client message and the current user authenticated. # If needed, a reference to the current context is provided along with the client message # with msg#handler. def process(client_input) client_message = ClientMessage.parse(client_input).with(self) handler_method = "handle_#{client_message.command}" if respond_to?(handler_method) send(handler_method, msg: client_message, user: @user) else write ServerMessage.new("KO Unhandled command \"#{client_message.command}\"", tag: client_message.tag) end rescue StandardError => e $logger.error e end # TODO: move this module module CommandHelpers attr_accessor :selected, :user def authentified? @user end def authenticate_user!(username:, password:) if (@user = User.authenticate(username: username, password: password)) true else false end end end include CommandHelpers end end