From 50ccb02e2f95b4f94400f08af07787d9cb086ce7 Mon Sep 17 00:00:00 2001 From: paul Date: Fri, 11 Nov 2005 14:56:19 +0000 Subject: [PATCH] Backend system rework: * Use two kind of backends: a domain backend and account backend. * Adapt the configuration for this change. * Changed the backend loading system via Backend.get. * Created two new namespaces: AccountBackend and DomainBackend. * Split up the Backend::Basic class into AccountBackend::Basic and DomainBackend. * Removed the Server::Account class, moved the methods to AccountBackend::Basic. * Created the Htaccess AccountBackend, moved the LDAPv3 backend to DomainBackend::Ldap. * Removed AccountBackend related stuff from DomainBackend::Ldap. git-svn-id: svn+ssh://svn.luon.net/svn/ildus/trunk@12 65a33f86-aa00-0410-91be-cd1bf5efb309 --- conf/ildusd.conf | 18 +- lib/ildus/server.rb | 2 - lib/ildus/server/account.rb | 53 ----- lib/ildus/server/account_backends/htaccess.rb | 17 ++ lib/ildus/server/backend.rb | 196 +++++++++++++----- .../{backends => domain_backends}/ldap.rb | 49 +++-- lib/ildus/server/handler.rb | 43 ++-- 7 files changed, 236 insertions(+), 142 deletions(-) delete mode 100644 lib/ildus/server/account.rb create mode 100644 lib/ildus/server/account_backends/htaccess.rb rename lib/ildus/server/{backends => domain_backends}/ldap.rb (61%) diff --git a/conf/ildusd.conf b/conf/ildusd.conf index cde9805..800322d 100644 --- a/conf/ildusd.conf +++ b/conf/ildusd.conf @@ -21,19 +21,21 @@ port: 9000 logdir: /var/log/ildusd # Pidfile to save the PID of the main daemon process to. -pidfile: /tmp/ - -# The password file. -passwd: /etc/ildusd/passwd +pidfile: /var/run/ildusd/ildusd.pid -### BACKEND +### BACKENDS # -# Backend to use and the backend's options/parameters. -backend: +# Backend to use for accessing the domain database. +domain: type: ldap host: ldap.somedomain.tld base: cn=somedomain.test,ou=DNS,dc=somedomain,dc=test user: cn=admin,ou=People,dc=somedomain,dc=test pass: secret - domain: somedomain.tld + name: somedomain.tld + +# Backend to use for the account database. +account: + type: htaccess + file: /etc/ildus/passwd diff --git a/lib/ildus/server.rb b/lib/ildus/server.rb index 855d8d5..f5d894d 100644 --- a/lib/ildus/server.rb +++ b/lib/ildus/server.rb @@ -29,8 +29,6 @@ module Ildus @config = nil parse_config(config_file) - Backend.load - super(config['port'], DEFAULT_HOST, 20, $stderr, true, false) end diff --git a/lib/ildus/server/account.rb b/lib/ildus/server/account.rb deleted file mode 100644 index 9d6a84c..0000000 --- a/lib/ildus/server/account.rb +++ /dev/null @@ -1,53 +0,0 @@ -# = ildus/server/account - server account library -# -# 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 'yaml' - -module Ildus - - class Server - - class Account - - attr_reader :user, :pass - - def self.register_account(user, pass) - end - - def self.unregister_account(user, pass) - end - - def initialize - @user = nil - @pass = nil - @auth = false - end - - def user=(username) - raise Handler::AlreadyAuthError if @auth - @user = username - end - - def pass=(password) - raise Handler::AlreadyAuthError if @auth - @pass = password - ## STUB - @auth = (password == "foo") - ## - end - - def authenticated? - @auth - end - - end # class Account - - end # class Server - -end # module Ildus diff --git a/lib/ildus/server/account_backends/htaccess.rb b/lib/ildus/server/account_backends/htaccess.rb new file mode 100644 index 0000000..2c40310 --- /dev/null +++ b/lib/ildus/server/account_backends/htaccess.rb @@ -0,0 +1,17 @@ +# = ildus/server/account_backends/htaccess - account backend library for .htacces files +# +# 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. + +module Ildus::Server::AccountBackend + + # = Account backend class for Apache's htaccess files. + class Htaccess < Basic + + end # class Htaccess + +end # module Ildus::Server::AccountBackend diff --git a/lib/ildus/server/backend.rb b/lib/ildus/server/backend.rb index 4f76f90..284f727 100644 --- a/lib/ildus/server/backend.rb +++ b/lib/ildus/server/backend.rb @@ -13,110 +13,208 @@ module Ildus class Server + # = Backend access module + # + # Module that is able to load and retrieve account or + # backend classes & code. module Backend + + # Load the plugin for account backend _name_ and return the class + # that can be instantiated. + def self.get_account(name) + get("account", AccountBackend, name) + end + + # Load the plugin for domain backend _name_ and return the class + # that can be instantiated. + def self.get_domain(name) + get("domain", DomainBackend, name) + end - BackendClasses = Hash.new + # Method that does the actual loading and class retrieving. + def self.get(type, mod, name) + name = name.to_s + path = File.dirname(__FILE__) + file = File.join(path, "#{type}_backends", name + ".rb") + puts "Requiring #{file} for #{type} backend: #{name}... " + require file - class << self + return mod.const_get(name.capitalize) + rescue NameError, LoadError + nil + end - def load - path = File.dirname(__FILE__) - Dir["#{path}/backends/**/*.rb"].each { |file| require file } - end + end # module Backend - def get(type) - BackendClasses[type.to_sym] - end - alias_method :[], :get - def register(type, backend_class) - if BackendClasses.include? type - raise "type #{type} already registered" - end - BackendClasses[type] = backend_class - end - - end # self - + # = Account administration backend module + # + # This module contains all account backends that are used for managing + # Ildus accounts in some database. All classes inherit from Basic, + # which defines the basic interface. + # + # For the interface of each class in the AccountBackend, see the Basic + # class. + module AccountBackend + + # = Basic account backend + # + # Class defining the basic interface of an account backend class. + # All account backend classes should inherit from this class. + # + # All methods marked with (*Interface*) raise Handler::NotImplementedError + # and should be overriden by a class implementing the interface. class Basic - attr_reader :config, :user, :pass + # Configuration for the backend (Hash). + attr_reader :config - def self.inherited(subclass) - diff = subclass.to_s.split('::') - self.to_s.split('::') - type = diff.to_s.split('::').last.downcase.to_sym - Backend.register(type, subclass) - end - + # The currently selected user. + attr_reader :user + + # The currently selected password. + attr_reader :pass + + # Boolean determining the authentication status. + attr_writer :auth + + # Creates a new account backend object, the current *user* + # and *password* are undefined by default. def initialize(backend_config) @config = backend_config - @auth = false - @user = user - @pass = pass + @user = nil + @pass = nil + @auth = false end - ################# - # Account methods - + # Sets the _username_. def user=(username) @user = username end + # Sets the _password_ and tries to set/review the authentication + # status via the interface (using #authenticate()). def pass=(password) @pass = password authenticate end + # Determines whether the backend considers the currently selected + # user with the currently selected password as authenticated. def authenticated? @auth end + + ################# + # Backend methods + + # (*Interface*) Considers the currently selected _user_ and + # _password_ and uses #auth= to set whether the user is + # authenticated or not. + def authenticate + raise Handler::NotImplementedError + end + + # (*Interface*) Registers a new account _user_ with password + # _pass_ in the database. + def register_account(user, pass) + raise Handler::NotImplementedError + end + + # (*Interface*) Removes the account of _user_ from the database. + def unregister_account(user) + raise Handler::NotImplementedError + end + + end # class Basic + + end # module AccountBackend + + + # = Domain administration backend module + # + # This module contains all domain backends that are used for managing + # Ildus domain records in some database. All classes inherit from + # Basic, which defines the basic interface. + # + # For the interface of each class in the DomainBackend, see the Basic + # class. + module DomainBackend + + # = Basic domain backend + # + # Class defining the basic interface of a domain backend class. + # All domain backend classes should inherit from this class. + # + # All methods marked with (*Interface*) raise Handler::NotImplementedError + # and should be overriden by a class implementing the interface. + class Basic + + # Configuration for the backend (Hash). + attr_accessor :config + + # The currently selected user. + attr_accessor :user + + # Creates a new backend object with the configuration + # _backend_config_. The current *user* is left undefined. + def initialize(backend_config) + @config = backend_config + @user = nil + end + + # Method called when all actions are done and the data should be + # committed to the database (if not done real-time already before). + # + # This method can be override, but should also call +super+, since + # this methods runs the optional hook. + def commit + system "#{@config['hook']}" if @config['hook'] + end ################# # Backend methods - def commit - raise Handler::NotImplementedError - end - - def authenticate - raise Handler::NotImplementedError - end - - def register_account - raise Handler::NotImplementedError - end - - def unregister_account - raise Handler::NotImplementedError - end - + # (*Interface*) Returns a datastructure containing + # the list of hosts owned by the selected user. def hosts + # FIXME: format constraints. raise Handler::NotImplementedError end + # (*Interface*) Adds the given _host_ to the list + # of owned hosts by the selected user. def add_host(host) raise Handler::NotImplementedError end + # (*Interface*) Removes the given _host_ from the + # list of owned hosts by the selected user. def remove_host(host) raise Handler::NotImplementedError end + # (*Interface*) Updates the address of the given + # _host_ to the give address _addr_. def update_host(host, addr) raise Handler::NotImplementedError end + # (*Interface*) Adds the alias _new_alias_ to the + # given _host_. def add_alias(new_alias, host) raise Handler::NotImplementedError end + # (*Interface*) Removes the alias _old_alias_ from + # the given _host_. def remove_alias(old_alias, host) raise Handler::NotImplementedError end end # class Basic - end # module Backend + end # module DomainBackend end # class Server diff --git a/lib/ildus/server/backends/ldap.rb b/lib/ildus/server/domain_backends/ldap.rb similarity index 61% rename from lib/ildus/server/backends/ldap.rb rename to lib/ildus/server/domain_backends/ldap.rb index a900f33..db80dd1 100644 --- a/lib/ildus/server/backends/ldap.rb +++ b/lib/ildus/server/domain_backends/ldap.rb @@ -1,4 +1,4 @@ -# = ildus/server/backend - generic server backend library +# = ildus/server/backend - ldap domain backend library # # Copyright (C) 2005 Paul van Tilburg # @@ -9,10 +9,19 @@ require 'ldap' -module Ildus::Server::Backend +module Ildus::Server::DomainBackend - class LDAPv3 < Basic + # = LDAP domain backend + # + # The domain backend class that uses an LDAP database. It uses the LDAP + # record format used by the PowerDNS backend. (see: + # http://www.linuxnetworks.de/pdnsldap/dnsdomain2.schema) Next to that it + # uses an extra ildusOwner field to couple LDAP dNSdomain records to + # users. + class Ldap < Basic + # Sets up the LDAP backend by switching to LDAPv3 protocol + # and binding to the server. def initialize(*args) super @ldap = LDAP::Conn.new(config['host']) @@ -20,17 +29,15 @@ module Ildus::Server::Backend @ldap.simple_bind(config['user'], config['pass']) end + # Updates the serial on the SOA record and calls the inherited + # commit (invoking the hook). def commit update_serial - system "#{@config['hook']}" if @config['hook'] - end - - def authenticate - ## STUB - @auth = (@pass == "foo") - ## + super end + # Returns the list (a Hash actually) of hosts for the selected user. + # It looks for A, AAAA and CNAME records. def hosts entries = Hash.new { |h, k| h[k] = [[], []] } @@ -51,6 +58,8 @@ module Ildus::Server::Backend return entries end # def hosts + # Updates the address of _host_ to _addr_ providing that _addr_ is in a + # correct IPv4 or IPv6 address format. def update_host(host, addr) entry = all_entries.find do |entry| entry['associatedDomain'][0] == host + "." + config['domain'] @@ -61,6 +70,8 @@ module Ildus::Server::Backend @ldap.modify(entry['dn'][0], {"aRecord" => [addr.to_s]}) elsif addr.ipv6? @ldap.modify(entry['dn'][0], {"aAAARecord" => [addr.to_s]}) + else + return false end true @@ -70,17 +81,28 @@ module Ildus::Server::Backend private ######### + # Returns a list of all LDAP IldusRecord entries for the selected + # user. def all_entries @ldap.search2(config['base'], LDAP::LDAP_SCOPE_SUBTREE, "(&(objectClass=ildusRecord)(ildusOwner=#{user}))") end + # Returns the LDAP sOARecord for the configured domain. def soa_record @ldap.search2(config['base'], LDAP::LDAP_SCOPE_BASE, "(&(associatedDomain=#{config['domain'].downcase})" \ " (sOARecord=*))").first end + # Updates the serial of the SOA record of the configured domain. + # The serial consists of two parts, the date (yyyymmdd) and + # counter (nn) part. On every update the date is set to the current + # date, it will increase the counter. + # + # Note that the counter may overflow so that actually the day is + # incremented. This is no problem unless there is an almost infinite + # sequance of days with more then 100 updates/day. def update_serial record = soa_record soa = record['sOARecord'].first.split @@ -89,7 +111,8 @@ module Ildus::Server::Backend @ldap.modify(record['dn'][0], {"sOARecord" => [soa.join(" ")]}) end - # FIXME: unit tests! + # Determines the successor of the current serial number, see + # also #update_serial(). def serial_succ(serial) date, num = serial.scan(/\d{8}|\d{2}/) today = Time.now.strftime("%Y%m%d") @@ -105,6 +128,6 @@ module Ildus::Server::Backend return "%08s%02s" % [date, num] end - end # class LDAPv3 + end # class Ldap -end # module Ildus::Server::Backend +end # module Ildus::Server::DomainBackend diff --git a/lib/ildus/server/handler.rb b/lib/ildus/server/handler.rb index 1b7e917..67abba3 100644 --- a/lib/ildus/server/handler.rb +++ b/lib/ildus/server/handler.rb @@ -68,10 +68,18 @@ module Ildus @config = server.config @io = io @commit = false - type = @config["backend"]["type"] - klass = Backend[@config["backend"]["type"]] - raise "backend type `#{type}' not found" if klass.nil? - @backend = klass.new(@config["backend"]) + + # 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 @@ -114,7 +122,7 @@ module Ildus prot_msg 505, msg raise ensure - @backend.commit if @commit + @domain.commit if @commit end ######### @@ -147,27 +155,28 @@ module Ildus # Commands methods def user_cmd(username) - raise AlreadyAuthError if @backend.authenticated? - @backend.user = username + raise AlreadyAuthError if @account.authenticated? + @account.user = username + @domain.user = username prot_msg 331 end def pass_cmd(password) - raise SetUserFirstError unless @backend.user - raise AlreadyAuthError if @backend.authenticated? - @backend.pass = password + raise SetUserFirstError unless @account.user + raise AlreadyAuthError if @account.authenticated? + @account.pass = password - if @backend.authenticated? - prot_msg 230, @backend.user + if @account.authenticated? + prot_msg 230, @account.user else raise NotAuthError end end def updt_cmd(host, addr) - raise NotAuthError unless @backend.authenticated? + raise NotAuthError unless @account.authenticated? addr = IPAddr.new(addr) - @commit ||= @backend.update_host(host, addr) + @commit ||= @domain.update_host(host, addr) rescue HostNotFoundError prot_msg 425, host rescue RecordNotFoundError @@ -185,9 +194,9 @@ module Ildus end def list_cmd - raise NotAuthError unless @backend.authenticated? - user = @backend.user - list = @backend.hosts.inject(Hash.new) do |memo, (host, info)| + 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