# = ildus/server/handler - Ildus server command handler # # Copyright (C) 2005 Paul van Tilburg # # Ildus is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. require 'ipaddr' require 'ildus/server/backend' module Ildus class Server < GServer class Handler class ProtocolException < StandardError; end class HandlerExit < ProtocolException; end # 221 class HostNotFoundError < ProtocolException; end # 425 class RecordNotFoundError < ProtocolException; end # 426 class CmdUnknownError < ProtocolException; end # 500 class ArgsSyntaxError < ProtocolException; end # 501 class NotImplementedError < ProtocolException; end # 502 class TooManyUnknownError < ProtocolException; end # 503 class AlreadyAuthError < ProtocolException; end # 504 class BackendError < ProtocolException; end # 506 class NotAuthError < ProtocolException; end # 530 class SetUserFirstError < ProtocolException; end # 531 MaxCmdErrs = 3 ProtocolCodeMapping = { 200 => "Command OK.", # 210, 211, 212, 213 214 => "Direct comments to %s.", 215 => "Listing done.", 220 => "%s %s %s %s, welcome.", 221 => "%s closed connection, goodbye!", 230 => "User %s authenticated, proceed.", 240 => "Updated host %s to IP %s.", # 330 331 => "User name OK, need password.", # 420 421 => "%s forcibly closed connection.", 422 => "connection timed out, %s forcibly closed connection.", # 423, 424 425 => "Can't find host %s.", 426 => "Can't find %s-record of host %s.", 500 => "Syntax error, command `%s' unrecognized.", 501 => "Syntax error in arguments.", 502 => "Command not implemented.", 503 => "Too many unrecognized commands!", 504 => "You are already authenticated!", 505 => "Server error: %s!", 506 => "Server error, update failed: %s", 530 => "Not authenticated!", 531 => "Login with USER first." } def initialize(server, io) @server = server @config = server.config @io = io @commit = false # Create the domain backend. type = @config["domain"]["type"] klass = Backend.get_domain(type) raise "domain backend type `#{type}' not found" if klass.nil? @domain = klass.new(@config["domain"]) # Create the account backend. type = @config["account"]["type"] klass = Backend.get_account(type) raise "account backend type `#{type}' not found" if klass.nil? @account = klass.new(@config["account"]) rescue => msg prot_msg 505, msg raise end def handle_client prot_msg 220, "localhost", Program, Version, Time.now.to_s cmd_errs = 0 @io.each_line do |line| begin cmd, *args = line.split handle_command(cmd, args) rescue HandlerExit prot_msg 221, "localhost" break rescue CmdUnknownError # 500 raise TooManyUnknownError if cmd_errs > MaxCmdErrs cmd_errs = cmd_errs + 1 prot_msg 500, cmd rescue ArgsSyntaxError # 501 raise TooManyUnknownError if cmd_errs > MaxCmdErrs cmd_errs = cmd_errs + 1 prot_msg 501 rescue NotImplementedError # 502 prot_msg 502 rescue AlreadyAuthError # 504 prot_msg 504 rescue BackendError => msg # 506 prot_msg 506, msg rescue NotAuthError # 530 prot_msg 530 rescue SetUserFirstError # 531 prot_msg 531 end end rescue TooManyUnknownError # 503 prot_msg 503 rescue RuntimeError => msg # 505 prot_msg 505, msg raise ensure @domain.commit if @commit end ######### private ######### def handle_command(cmd, args=[]) method_name = cmd.downcase + "_cmd" meth = method(method_name) meth[*args] rescue ArgumentError raise ArgsSyntaxError rescue NameError => e $stderr.puts "#{e.class}: #{e}" $stderr.puts e.backtrace raise CmdUnknownError end def prot_msg(code, *args) msg = code.to_s + " " + (ProtocolCodeMapping[code] % args) @io.puts msg end def prot_msg_with_body(code, body, *args) @io.print code.to_s, "-", body prot_msg(code, *args) end ################## # Commands methods def user_cmd(username) raise AlreadyAuthError if @account.authenticated? @account.user = username @domain.user = username prot_msg 331 end def pass_cmd(password) raise SetUserFirstError unless @account.user raise AlreadyAuthError if @account.authenticated? @account.pass = password if @account.authenticated? prot_msg 230, @account.user else raise NotAuthError end end def updt_cmd(host, addr) raise NotAuthError unless @account.authenticated? addr = IPAddr.new(addr) @commit ||= @domain.update_host(host, addr) rescue HostNotFoundError prot_msg 425, host rescue RecordNotFoundError prot_msg 426, type, host else prot_msg 240, host, addr end def adda_cmd(host, new_alias) raise NotImplementedError end def dela_cmd(host, old_alias) raise NotImplementedError end def list_cmd raise NotAuthError unless @account.authenticated? user = @account.user list = @domain.hosts.inject(Hash.new) do |memo, (host, info)| memo[host] = {"addresses" => info.first, "aliases" => info.last} memo end prot_msg_with_body 215, "Listing of hosts (and aliases) for user #{user}\n" + list.to_yaml + "\n" end def help_cmd prot_msg_with_body 214, <<-EOT, "ildus-admin@localhost" The following commands are recognized: HELP\t\t\tshow this help QUIT\t\t\tclose connection USER \t\tsupply username PASS \t\tgive password LIST\t\t\tlist hostnames (and aliases)\t[auth. required] UPDT \tupdate ip for host\t\t[auth. required] ADDA \tadd alias to host\t\t[auth. required] DELA \tdelete alias from host\t\t[auth. required] EOT end def quit_cmd raise HandlerExit end end # class Handler end # class Server end # module Ildus