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
This commit is contained in:
paul 2005-11-11 14:56:19 +00:00
parent 7b7b4c6631
commit 50ccb02e2f
7 changed files with 236 additions and 142 deletions

View File

@ -21,19 +21,21 @@ port: 9000
logdir: /var/log/ildusd logdir: /var/log/ildusd
# Pidfile to save the PID of the main daemon process to. # Pidfile to save the PID of the main daemon process to.
pidfile: /tmp/ pidfile: /var/run/ildusd/ildusd.pid
# The password file.
passwd: /etc/ildusd/passwd
### BACKEND ### BACKENDS
# #
# Backend to use and the backend's options/parameters. # Backend to use for accessing the domain database.
backend: domain:
type: ldap type: ldap
host: ldap.somedomain.tld host: ldap.somedomain.tld
base: cn=somedomain.test,ou=DNS,dc=somedomain,dc=test base: cn=somedomain.test,ou=DNS,dc=somedomain,dc=test
user: cn=admin,ou=People,dc=somedomain,dc=test user: cn=admin,ou=People,dc=somedomain,dc=test
pass: secret pass: secret
domain: somedomain.tld name: somedomain.tld
# Backend to use for the account database.
account:
type: htaccess
file: /etc/ildus/passwd

View File

@ -29,8 +29,6 @@ module Ildus
@config = nil @config = nil
parse_config(config_file) parse_config(config_file)
Backend.load
super(config['port'], DEFAULT_HOST, 20, $stderr, true, false) super(config['port'], DEFAULT_HOST, 20, $stderr, true, false)
end end

View File

@ -1,53 +0,0 @@
# = ildus/server/account - server account library
#
# Copyright (C) 2005 Paul van Tilburg <paul@luon.net>
#
# 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

View File

@ -0,0 +1,17 @@
# = ildus/server/account_backends/htaccess - account backend library for .htacces files
#
# Copyright (C) 2005 Paul van Tilburg <paul@luon.net>
#
# 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

View File

@ -13,110 +13,208 @@ module Ildus
class Server class Server
# = Backend access module
#
# Module that is able to load and retrieve account or
# backend classes & code.
module Backend 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 end # module Backend
path = File.dirname(__FILE__)
Dir["#{path}/backends/**/*.rb"].each { |file| require file }
end
def get(type)
BackendClasses[type.to_sym]
end
alias_method :[], :get
def register(type, backend_class) # = Account administration backend module
if BackendClasses.include? type #
raise "type #{type} already registered" # This module contains all account backends that are used for managing
end # Ildus accounts in some database. All classes inherit from Basic,
BackendClasses[type] = backend_class # which defines the basic interface.
end #
# For the interface of each class in the AccountBackend, see the Basic
end # self # 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 class Basic
attr_reader :config, :user, :pass # Configuration for the backend (Hash).
attr_reader :config
def self.inherited(subclass) # The currently selected user.
diff = subclass.to_s.split('::') - self.to_s.split('::') attr_reader :user
type = diff.to_s.split('::').last.downcase.to_sym
Backend.register(type, subclass) # The currently selected password.
end 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) def initialize(backend_config)
@config = backend_config @config = backend_config
@auth = false @user = nil
@user = user @pass = nil
@pass = pass @auth = false
end end
################# # Sets the _username_.
# Account methods
def user=(username) def user=(username)
@user = username @user = username
end end
# Sets the _password_ and tries to set/review the authentication
# status via the interface (using #authenticate()).
def pass=(password) def pass=(password)
@pass = password @pass = password
authenticate authenticate
end end
# Determines whether the backend considers the currently selected
# user with the currently selected password as authenticated.
def authenticated? def authenticated?
@auth @auth
end 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 # Backend methods
def commit # (*Interface*) Returns a datastructure containing
raise Handler::NotImplementedError # the list of hosts owned by the selected user.
end
def authenticate
raise Handler::NotImplementedError
end
def register_account
raise Handler::NotImplementedError
end
def unregister_account
raise Handler::NotImplementedError
end
def hosts def hosts
# FIXME: format constraints.
raise Handler::NotImplementedError raise Handler::NotImplementedError
end end
# (*Interface*) Adds the given _host_ to the list
# of owned hosts by the selected user.
def add_host(host) def add_host(host)
raise Handler::NotImplementedError raise Handler::NotImplementedError
end end
# (*Interface*) Removes the given _host_ from the
# list of owned hosts by the selected user.
def remove_host(host) def remove_host(host)
raise Handler::NotImplementedError raise Handler::NotImplementedError
end end
# (*Interface*) Updates the address of the given
# _host_ to the give address _addr_.
def update_host(host, addr) def update_host(host, addr)
raise Handler::NotImplementedError raise Handler::NotImplementedError
end end
# (*Interface*) Adds the alias _new_alias_ to the
# given _host_.
def add_alias(new_alias, host) def add_alias(new_alias, host)
raise Handler::NotImplementedError raise Handler::NotImplementedError
end end
# (*Interface*) Removes the alias _old_alias_ from
# the given _host_.
def remove_alias(old_alias, host) def remove_alias(old_alias, host)
raise Handler::NotImplementedError raise Handler::NotImplementedError
end end
end # class Basic end # class Basic
end # module Backend end # module DomainBackend
end # class Server end # class Server

View File

@ -1,4 +1,4 @@
# = ildus/server/backend - generic server backend library # = ildus/server/backend - ldap domain backend library
# #
# Copyright (C) 2005 Paul van Tilburg <paul@luon.net> # Copyright (C) 2005 Paul van Tilburg <paul@luon.net>
# #
@ -9,10 +9,19 @@
require 'ldap' 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) def initialize(*args)
super super
@ldap = LDAP::Conn.new(config['host']) @ldap = LDAP::Conn.new(config['host'])
@ -20,17 +29,15 @@ module Ildus::Server::Backend
@ldap.simple_bind(config['user'], config['pass']) @ldap.simple_bind(config['user'], config['pass'])
end end
# Updates the serial on the SOA record and calls the inherited
# commit (invoking the hook).
def commit def commit
update_serial update_serial
system "#{@config['hook']}" if @config['hook'] super
end
def authenticate
## STUB
@auth = (@pass == "foo")
##
end end
# Returns the list (a Hash actually) of hosts for the selected user.
# It looks for A, AAAA and CNAME records.
def hosts def hosts
entries = Hash.new { |h, k| h[k] = [[], []] } entries = Hash.new { |h, k| h[k] = [[], []] }
@ -51,6 +58,8 @@ module Ildus::Server::Backend
return entries return entries
end # def hosts 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) def update_host(host, addr)
entry = all_entries.find do |entry| entry = all_entries.find do |entry|
entry['associatedDomain'][0] == host + "." + config['domain'] entry['associatedDomain'][0] == host + "." + config['domain']
@ -61,6 +70,8 @@ module Ildus::Server::Backend
@ldap.modify(entry['dn'][0], {"aRecord" => [addr.to_s]}) @ldap.modify(entry['dn'][0], {"aRecord" => [addr.to_s]})
elsif addr.ipv6? elsif addr.ipv6?
@ldap.modify(entry['dn'][0], {"aAAARecord" => [addr.to_s]}) @ldap.modify(entry['dn'][0], {"aAAARecord" => [addr.to_s]})
else
return false
end end
true true
@ -70,17 +81,28 @@ module Ildus::Server::Backend
private private
######### #########
# Returns a list of all LDAP IldusRecord entries for the selected
# user.
def all_entries def all_entries
@ldap.search2(config['base'], LDAP::LDAP_SCOPE_SUBTREE, @ldap.search2(config['base'], LDAP::LDAP_SCOPE_SUBTREE,
"(&(objectClass=ildusRecord)(ildusOwner=#{user}))") "(&(objectClass=ildusRecord)(ildusOwner=#{user}))")
end end
# Returns the LDAP sOARecord for the configured domain.
def soa_record def soa_record
@ldap.search2(config['base'], LDAP::LDAP_SCOPE_BASE, @ldap.search2(config['base'], LDAP::LDAP_SCOPE_BASE,
"(&(associatedDomain=#{config['domain'].downcase})" \ "(&(associatedDomain=#{config['domain'].downcase})" \
" (sOARecord=*))").first " (sOARecord=*))").first
end 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 def update_serial
record = soa_record record = soa_record
soa = record['sOARecord'].first.split soa = record['sOARecord'].first.split
@ -89,7 +111,8 @@ module Ildus::Server::Backend
@ldap.modify(record['dn'][0], {"sOARecord" => [soa.join(" ")]}) @ldap.modify(record['dn'][0], {"sOARecord" => [soa.join(" ")]})
end end
# FIXME: unit tests! # Determines the successor of the current serial number, see
# also #update_serial().
def serial_succ(serial) def serial_succ(serial)
date, num = serial.scan(/\d{8}|\d{2}/) date, num = serial.scan(/\d{8}|\d{2}/)
today = Time.now.strftime("%Y%m%d") today = Time.now.strftime("%Y%m%d")
@ -105,6 +128,6 @@ module Ildus::Server::Backend
return "%08s%02s" % [date, num] return "%08s%02s" % [date, num]
end end
end # class LDAPv3 end # class Ldap
end # module Ildus::Server::Backend end # module Ildus::Server::DomainBackend

View File

@ -68,10 +68,18 @@ module Ildus
@config = server.config @config = server.config
@io = io @io = io
@commit = false @commit = false
type = @config["backend"]["type"]
klass = Backend[@config["backend"]["type"]] # Create the domain backend.
raise "backend type `#{type}' not found" if klass.nil? type = @config["domain"]["type"]
@backend = klass.new(@config["backend"]) 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 rescue => msg
prot_msg 505, msg prot_msg 505, msg
raise raise
@ -114,7 +122,7 @@ module Ildus
prot_msg 505, msg prot_msg 505, msg
raise raise
ensure ensure
@backend.commit if @commit @domain.commit if @commit
end end
######### #########
@ -147,27 +155,28 @@ module Ildus
# Commands methods # Commands methods
def user_cmd(username) def user_cmd(username)
raise AlreadyAuthError if @backend.authenticated? raise AlreadyAuthError if @account.authenticated?
@backend.user = username @account.user = username
@domain.user = username
prot_msg 331 prot_msg 331
end end
def pass_cmd(password) def pass_cmd(password)
raise SetUserFirstError unless @backend.user raise SetUserFirstError unless @account.user
raise AlreadyAuthError if @backend.authenticated? raise AlreadyAuthError if @account.authenticated?
@backend.pass = password @account.pass = password
if @backend.authenticated? if @account.authenticated?
prot_msg 230, @backend.user prot_msg 230, @account.user
else else
raise NotAuthError raise NotAuthError
end end
end end
def updt_cmd(host, addr) def updt_cmd(host, addr)
raise NotAuthError unless @backend.authenticated? raise NotAuthError unless @account.authenticated?
addr = IPAddr.new(addr) addr = IPAddr.new(addr)
@commit ||= @backend.update_host(host, addr) @commit ||= @domain.update_host(host, addr)
rescue HostNotFoundError rescue HostNotFoundError
prot_msg 425, host prot_msg 425, host
rescue RecordNotFoundError rescue RecordNotFoundError
@ -185,9 +194,9 @@ module Ildus
end end
def list_cmd def list_cmd
raise NotAuthError unless @backend.authenticated? raise NotAuthError unless @account.authenticated?
user = @backend.user user = @account.user
list = @backend.hosts.inject(Hash.new) do |memo, (host, info)| list = @domain.hosts.inject(Hash.new) do |memo, (host, info)|
memo[host] = {"addresses" => info.first, "aliases" => info.last} memo[host] = {"addresses" => info.first, "aliases" => info.last}
memo memo
end end