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:
parent
7b7b4c6631
commit
50ccb02e2f
7 changed files with 236 additions and 142 deletions
|
@ -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
|
||||
|
|
|
@ -29,8 +29,6 @@ module Ildus
|
|||
@config = nil
|
||||
parse_config(config_file)
|
||||
|
||||
Backend.load
|
||||
|
||||
super(config['port'], DEFAULT_HOST, 20, $stderr, true, false)
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
17
lib/ildus/server/account_backends/htaccess.rb
Normal file
17
lib/ildus/server/account_backends/htaccess.rb
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Reference in a new issue