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
# 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

View File

@ -29,8 +29,6 @@ module Ildus
@config = nil
parse_config(config_file)
Backend.load
super(config['port'], DEFAULT_HOST, 20, $stderr, true, false)
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
# = 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

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>
#
@ -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

View File

@ -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