summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Palfrader <peter@palfrader.org>2006-10-18 11:33:32 +0000
committerweasel <weasel@bc3d92e2-beff-0310-a7cd-cc87d7ac0ede>2006-10-18 11:33:32 +0000
commitc88bc35f1c88d9fbbba6706a4abaad24a1868c98 (patch)
tree487c31421b2f92e6e76bcf946500187b6b014e91
Add hosting ldap
git-svn-id: svn+ssh://asteria.noreply.org/svn/weaselutils/trunk@190 bc3d92e2-beff-0310-a7cd-cc87d7ac0ede
-rwxr-xr-xbin/ldap.add.client114
-rwxr-xr-xbin/ldap.add.dns49
-rwxr-xr-xbin/ldap.add.dns.secondary57
-rwxr-xr-xbin/ldap.add.ftp76
-rwxr-xr-xbin/ldap.add.mail.domain66
-rwxr-xr-xbin/ldap.add.mail.localperson70
-rwxr-xr-xbin/ldap.add.mail.remoteperson65
-rwxr-xr-xbin/ldap.add.mail.uucp67
-rwxr-xr-xbin/ldap.add.pg60
-rwxr-xr-xbin/ldap.add.staff51
-rwxr-xr-xbin/ldap.add.vhost63
-rwxr-xr-xbin/ldap2analog178
-rwxr-xr-xbin/ldap2apache236
-rwxr-xr-xbin/ldap2bind344
-rwxr-xr-xbin/ldap2chroot-accounts139
-rwxr-xr-xbin/ldap2mail29
-rwxr-xr-xbin/ldap2passwd94
-rwxr-xr-xbin/ldap2postgres138
-rwxr-xr-xbin/ldap2uucp99
-rw-r--r--schema/3node2.schema943
-rw-r--r--site-ruby/myldap.rb88
21 files changed, 3026 insertions, 0 deletions
diff --git a/bin/ldap.add.client b/bin/ldap.add.client
new file mode 100755
index 0000000..95f7f14
--- /dev/null
+++ b/bin/ldap.add.client
@@ -0,0 +1,114 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+def usage
+ puts "Usage: "+$0+" --help | --client <client> [--password <password>] [--description <description>]"
+end
+
+print_usage = false
+client = nil
+password = [File.new("/dev/urandom").read(config['module']['client']['pwlen'])].pack("m").chomp.delete('=')
+description = nil
+begin
+ GetoptLong.new(
+ [ "--help" , "-h", GetoptLong::NO_ARGUMENT ],
+ [ "--client" , "-c", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--password" , "-p", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--description" , "-D", GetoptLong::REQUIRED_ARGUMENT ]
+ ).each { |option, argument|
+ case option
+ when "--help"
+ print_usage = true
+ when "--client"
+ client = argument
+ when "--password"
+ password = argument
+ when "--description"
+ description = argument
+ else
+ raise("Unexpected option "+option);
+ end
+ }
+rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument, GetoptLong::NeedlessArgument
+ usage
+ exit 1;
+end
+
+if print_usage or (ARGV.length > 0) or (!client) or (!password)
+ usage
+ exit 0 if print_usage
+ exit 1
+end
+
+
+ldap = MyLDAP.new(config)
+
+
+# searching new uids
+newuid = config['module']['client']['minuid']
+begin
+ ldap.conn.search(config['basedn'], LDAP::LDAP_SCOPE_SUBTREE,
+ '(objectclass=tnClient)') {|e|
+
+ thiscn = e.vals("cn").pop;
+ thisuid = e.vals("uidNumber").pop.to_i;
+ thisgid = e.vals("gidNumber").pop.to_i;
+
+ STDERR.puts("warning: uid/gid mismatch for client "+thiscn) unless thisuid == thisgid;
+
+ thisuid = thisuid > thisgid ? thisuid : thisgid
+ newuid = newuid > thisuid ? newuid : thisuid;
+ }
+rescue LDAP::ResultError => msg
+ $stderr.print(msg)
+ exit 1;
+end
+
+newuid += 1
+
+data = {
+ 'objectclass' => ['top', 'tnClient', 'posixAccount', 'posixGroup'],
+ 'o' => [client],
+ 'userPassword' => [password],
+ 'homeDirectory' => [ config['module']['client']['basehome'] + '/' + client ],
+ 'cn' => [ 'W' + client ],
+ 'uid' => [ 'W' + client ],
+ 'uidNumber' => [ newuid.to_s ],
+ 'gidNumber' => [ newuid.to_s ]
+}
+data['description'] = [description] if description
+
+dn = "o=%s,ou=hosting,%s"%[client, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
+
+%w(mail vhosts ftp dns).each{ |ou|
+ ldap.add("ou="+ou+","+dn, {
+ 'objectclass' => ['top', 'organizationalUnit'],
+ 'ou' => [ou]})
+}
+%w(people domains uucp).each{ |ou|
+ ldap.add("ou="+ou+",ou=mail,"+dn, {
+ 'objectclass' => ['top', 'organizationalUnit'],
+ 'ou' => [ou]})
+}
+#ldap.add("ou=postgresql,"+dn, {
+# 'objectclass' => ['top', 'organizationalUnit', 'tnPostgreSQLdatabase'],
+# 'ou' => ['postgresql'],
+# 'cn' => [client] })
diff --git a/bin/ldap.add.dns b/bin/ldap.add.dns
new file mode 100755
index 0000000..58896ea
--- /dev/null
+++ b/bin/ldap.add.dns
@@ -0,0 +1,49 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-d", "--domain=domain" , String, "Domain name") { |@domainname| }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @domainname
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+
+data = {
+ 'objectclass' => ['top', 'tnDNSsoa'],
+ 'tnDNSdomainname' => [ @domainname ]
+}
+data['description'] = [@description] if @description
+
+
+dn = "tnDNSdomainname=%s,ou=dns,o=%s,ou=hosting,%s"%[@domainname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.dns.secondary b/bin/ldap.add.dns.secondary
new file mode 100755
index 0000000..0d456bc
--- /dev/null
+++ b/bin/ldap.add.dns.secondary
@@ -0,0 +1,57 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@nss = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-d", "--domain=domain" , String, "Domain name") { |@domainname| }
+ opts.on("-m", "--master=master" , String, "IP of Master") { |@master| }
+ opts.on("-n", "--ns=ns" , String, "our Namservers that are slaves") { |val| @nss << val }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @domainname
+show_help(ARGV.options, 1, STDERR) unless @master
+@nss = config['module']['bind']['nameservers'] unless @nss.length > 0
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+
+data = {
+ 'objectclass' => ['top', 'tnDNSsecondary'],
+ 'tnDNSdomainname' => [ @domainname ],
+ 'tnDNSprimary' => [ @master ],
+ 'tnDNSnameservers' => @nss
+}
+data['description'] = [@description] if @description
+
+
+dn = "tnDNSdomainname=%s,ou=dns-slaves,o=%s,ou=hosting,%s"%[@domainname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.ftp b/bin/ldap.add.ftp
new file mode 100755
index 0000000..93fb138
--- /dev/null
+++ b/bin/ldap.add.ftp
@@ -0,0 +1,76 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@password = [File.new("/dev/urandom").read(config['module']['ftp']['pwlen'])].pack("m").chomp.delete('=')
+@sshkeys = []
+@hosts = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-b", "--basedir=DIRECTORY", String, "Account basedirectory") { |@basedir| }
+ opts.on("-a", "--accountname=NAME" , String, "Account (base)name") { |@accountname| }
+ opts.on("-p", "--password=PASSWORD", String, "Password") { |@password| }
+ opts.on("-s", "--ssh-key=SSH-KEY" , String, "ssh public key") { |val| @sshkeys.push(val) }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts.push(val) }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @basedir
+show_help(ARGV.options, 1, STDERR) unless @accountname
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+
+@accountname = @clientname+'.'+@accountname
+@pamHomedirectory = config['module']['ftp']['chrootBaseDir']+'/'+@accountname
+@realDatadirectory = (@basedir =~ /^\// ? '' : client['homeDirectory'][0]) + '/' + @basedir
+
+@pamHomedirectory.gsub!('//', '/') while @pamHomedirectory.index('//') # scponly gives special meaning to //
+@realDatadirectory.gsub!('//', '/') while @realDatadirectory.index('//') # scponly gives special meaning to //
+
+data = {
+ 'objectclass' => ['top', 'tnFTPuser', 'posixAccount'],
+ 'cn' => [ @accountname ],
+ 'uid' => [ @accountname ],
+ 'uidNumber' => client['uidNumber'],
+ 'gidNumber' => client['gidNumber'],
+ 'homeDirectory' => [ @pamHomedirectory ],
+ 'tnFTPDataDirectory' => [ @realDatadirectory ],
+ 'userPassword' => [ @password ],
+ 'tnHost' => @hosts,
+ 'loginShell' => [ config['module']['ftp']['loginShell'] ]
+
+}
+data['tnSSHKey'] = [@sshkeys] if @sshkeys.length > 0
+data['description'] = [@description] if @description
+
+
+dn = "cn=%s,ou=ftp,o=%s,ou=hosting,%s"%[@accountname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.mail.domain b/bin/ldap.add.mail.domain
new file mode 100755
index 0000000..b50b353
--- /dev/null
+++ b/bin/ldap.add.mail.domain
@@ -0,0 +1,66 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@local = []
+@remote = []
+@hosts = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-d", "--domain=domain" , String, "Domain name") { |@domainname| }
+ opts.on("-M", "--no-magic-dns" , String, "Turn off Magic DNS") { @no_magic_dns = true }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts << val }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @domainname
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+@localaddresses = []
+%w(postmaster hostmaster abuse security root).each{ |l|
+ @localaddresses << l+"@"+@domainname
+}
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+
+data = {
+ 'objectclass' => ['top', 'tnMailDomain', 'tnMailRemotePerson'],
+ 'tnMailDomainname' => [ @domainname ],
+ 'tnMagicDNS' => [ @no_magic_dns ? "no" : "yes" ],
+ 'tnHost' => @hosts,
+ 'tnMailLocalAddress' => @localaddresses,
+ 'tnMailRemoteAddress' => ['roles']
+
+}
+data['description'] = [@description] if @description
+
+
+dn = "tnMailDomainname=%s,ou=domains,ou=mail,o=%s,ou=hosting,%s"%[@domainname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.mail.localperson b/bin/ldap.add.mail.localperson
new file mode 100755
index 0000000..053e4c9
--- /dev/null
+++ b/bin/ldap.add.mail.localperson
@@ -0,0 +1,70 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@local = []
+@hosts = []
+@password = [File.new("/dev/urandom").read(config['module']['mail']['pwlen'])].pack("m").chomp.delete('=')
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-n", "--name=Real Name" , String, "Person's full name") { |@realname| }
+ opts.on("-l", "--local=addr" , String, "Local Email Address (on this host)") { |val| @local << val }
+ opts.on("-a", "--accountname=name" , String, "Accountname (client.<name>)") { |@accountname| }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts << val }
+ opts.on("-p", "--password=PASSWORD" , String, "Password") { |@password| }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @realname
+show_help(ARGV.options, 1, STDERR) unless @local.length > 0
+show_help(ARGV.options, 1, STDERR) unless @accountname
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+ldap.verify_local_domains_exist(@local)
+
+data = {
+ 'objectclass' => ['top', 'tnMailPerson', 'tnMailAccount'],
+ 'cn' => [ @realname ],
+ 'tnMailLocalAddress' => @local,
+ 'tnHost' => @hosts,
+
+ 'tnMailMailboxAccountname' => [ "mail." + @clientname +"." +@accountname ],
+ 'tnMailVirtualAddress' => [ "mail." + @clientname +"." +@accountname +'@' + config['module']['mail']['virtualdomain'] ],
+ 'tnMailMailboxHomedir' => [ config['module']['mail']['basedir'] ],
+ 'tnMailMailboxLocation' => [ @clientname + '/' + @accountname + '/' ],
+ 'tnMailMailboxUID' => client['uidNumber'],
+ 'tnMailMailboxGID' => client['gidNumber'],
+ 'userPassword' => [ @password ]
+}
+data['description'] = [@description] if @description
+
+dn = "cn=%s,ou=people,ou=mail,o=%s,ou=hosting,%s"%[@realname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.mail.remoteperson b/bin/ldap.add.mail.remoteperson
new file mode 100755
index 0000000..5f3df3a
--- /dev/null
+++ b/bin/ldap.add.mail.remoteperson
@@ -0,0 +1,65 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@local = []
+@remote = []
+@hosts = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-n", "--name=Real Name" , String, "Person's full name") { |@realname| }
+ opts.on("-l", "--local=addr" , String, "Local Email Address (on this host)") { |val| @local << val }
+ opts.on("-r", "--remote=addr" , String, "Remote Email Address (where to forward to)") { |val| @remote << val }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts << val }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @realname
+show_help(ARGV.options, 1, STDERR) unless @local.length > 0
+show_help(ARGV.options, 1, STDERR) unless @remote.length > 0
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+ldap.verify_local_domains_exist(@local)
+
+data = {
+ 'objectclass' => ['top', 'tnMailPerson', 'tnMailRemotePerson'],
+ 'cn' => [ @realname ],
+ 'tnMailLocalAddress' => @local,
+ 'tnHost' => @hosts,
+
+ 'tnMailRemoteAddress' => @remote
+
+}
+data['description'] = [@description] if @description
+
+
+dn = "cn=%s,ou=people,ou=mail,o=%s,ou=hosting,%s"%[@realname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.mail.uucp b/bin/ldap.add.mail.uucp
new file mode 100755
index 0000000..cbb170c
--- /dev/null
+++ b/bin/ldap.add.mail.uucp
@@ -0,0 +1,67 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "optparse"
+require "myldap"
+require "yaml"
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@password = [File.new("/dev/urandom").read(config['module']['uucp']['pwlen'])].pack("m").chomp.delete('=')
+@domainname = []
+@hosts = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-d", "--domain=domain" , String, "Domain name") { |val| @domainname << val }
+ opts.on("-s", "--sysname=sysname" , String, "UUCP System Name") { |@sysname| }
+ opts.on("-k", "--ssh-key=ssh-key" , String, "SSH Key") { |@sshkey| }
+ opts.on("-M", "--no-magic-dns" , String, "Turn off Magic DNS") { @no_magic_dns = true }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts << val }
+ opts.on("-p", "--password=PASSWORD" , String, "Password") { |@password| }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @domainname.length > 0
+show_help(ARGV.options, 1, STDERR) unless @sysname
+show_help(ARGV.options, 1, STDERR) unless @sshkey
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+
+
+
+data = {
+ 'objectclass' => ['top', 'tnUUCPSystem'],
+ 'tnUUCPSysName' => [@sysname],
+ 'tnUUCPPassword' => [@password],
+ 'tnMagicDNS' => [ @no_magic_dns ? "no" : "yes" ],
+ 'tnHost' => @hosts,
+ 'tnMailDomainname' => @domainname,
+ 'tnSSHKey' => [@sshkey],
+ 'tnMailTransportDestination' => [ 'uucp:'+@sysname ]
+}
+data['description'] = [@description] if @description
+
+
+dn = "tnUUCPSysName=%s,ou=uucp,ou=mail,o=%s,ou=hosting,%s"%[@sysname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap = MyLDAP.new(config)
+ldap.add(dn, data)
diff --git a/bin/ldap.add.pg b/bin/ldap.add.pg
new file mode 100755
index 0000000..1d53510
--- /dev/null
+++ b/bin/ldap.add.pg
@@ -0,0 +1,60 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@password = [File.new("/dev/urandom").read(config['module']['pg']['pwlen'])].pack("m").chomp.delete('=')
+@sshkeys = []
+@hosts = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-a", "--accountname=NAME" , String, "Account (base)name") { |@accountname| }
+ opts.on("-p", "--password=PASSWORD", String, "Password") { |@password| }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts.push(val) }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @accountname
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+
+@accountname = @clientname+'_'+@accountname
+
+data = {
+ 'objectclass' => ['top', 'uidObject', 'simpleSecurityObject', 'tnPostgreSQLuser'],
+ 'uid' => [ @accountname ],
+ 'userPassword' => [ @password ],
+ 'tnHost' => @hosts
+
+}
+data['description'] = [@description] if @description
+
+dn = "uid=%s,ou=postgresql,o=%s,ou=hosting,%s"%[@accountname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap.add.staff b/bin/ldap.add.staff
new file mode 100755
index 0000000..675c416
--- /dev/null
+++ b/bin/ldap.add.staff
@@ -0,0 +1,51 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "optparse"
+require "myldap"
+require "yaml"
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+print_usage = false
+is_admin = false
+username = nil
+password = [File.new("/dev/urandom").read(config['module']['staff']['pwlen'])].pack("m").chomp.delete('=')
+description = nil
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-u", "--username=USERNAME" , String, "User name") { |username| }
+ opts.on("-p", "--password=PASSWORD" , String, "Password") { |password| }
+ opts.on("-a", "--admin" , String, "user is an admin") { is_admin = true }
+ opts.on("-D", "--description=BLA" , String, "description") { |description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless username
+
+data = {
+ 'objectclass' => ['top', 'organizationalRole', 'simpleSecurityObject'],
+ 'cn' => [username],
+ 'userPassword' => [password]
+}
+data['description'] = [description] if description
+
+dn = "cn=%s,%sou=staff,%s"%[username, is_admin ? 'ou=admins,' : '', config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+MyLDAP.new(config).add(dn, data)
diff --git a/bin/ldap.add.vhost b/bin/ldap.add.vhost
new file mode 100755
index 0000000..e6c169b
--- /dev/null
+++ b/bin/ldap.add.vhost
@@ -0,0 +1,63 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require 'optparse'
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@hosts = []
+@alias = []
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-c", "--client=CLIENT" , String, "Client Identifier") { |@clientname| }
+ opts.on("-h", "--hostname=HOSTNAME", String, "vhostname") { |@vhostname| }
+ opts.on("-a", "--alias=HOSTNAME" , String, "aliasname") { |val| @alias << val }
+ opts.on("-w", "--webmaster=EMAIL" , String, "email address of webmaster/mistress") { |@webmaster| }
+ opts.on("-M", "--no-magic-dns" , String, "Turn off Magic DNS") { @no_magic_dns = true }
+ opts.on("-H", "--host=HOST" , String, "active host") { |val| @hosts << val }
+ opts.on("-D", "--description=BLA" , String, "description") { |@description| }
+ opts.parse!
+end
+
+show_help(ARGV.options, 1, STDERR) if ARGV.length > 0
+show_help(ARGV.options, 1, STDERR) unless @clientname
+show_help(ARGV.options, 1, STDERR) unless @vhostname
+show_help(ARGV.options, 1, STDERR) unless @webmaster
+@hosts.push(config['defaulthost']) unless @hosts.length > 0
+
+ldap = MyLDAP.new(config)
+client = ldap.verify_client(@clientname)
+
+data = {
+ 'objectclass' => ['top', 'tnWebVHost'],
+ 'tnWebVHostServerName' => [ @vhostname ],
+ 'tnWebVHostHomeDirectory' => [ @vhostname ],
+ 'tnMagicDNS' => [ @no_magic_dns ? "no" : "yes" ],
+ 'tnWebVHostWebmaster' => [ @webmaster ],
+ 'tnHost' => @hosts
+}
+data['tnWebVHostServerAlias'] = @alias if @alias.length > 0
+data['description'] = [@description] if @description
+
+
+dn = "tnWebVHostServerName=%s,ou=vhosts,o=%s,ou=hosting,%s"%[@vhostname, @clientname, config['basedn']]
+
+puts dn
+puts data.to_yaml
+puts
+
+ldap.add(dn, data)
diff --git a/bin/ldap2analog b/bin/ldap2analog
new file mode 100755
index 0000000..15b2bc6
--- /dev/null
+++ b/bin/ldap2analog
@@ -0,0 +1,178 @@
+#!/usr/bin/perl -wT
+#
+# vim:set ts=4:
+# vim:set shiftwidth=4:
+
+=pod
+
+=head1 NAME
+
+ldap2analog - create analog config files
+
+=head1 SYNOPSIS
+
+ldap2apache [-vnshf]
+
+=head1 DESCRIPTION
+
+ldap2apache reads the configuration from LDAP and writes
+config files for analog
+
+=head1 OPTIONS
+
+=over
+
+=item -v
+
+Be verbose.
+
+=back
+
+=head1 AUTHOR
+
+Peter Palfrader E<lt>pp@3node.com<gt>
+
+=head1 FILES
+
+/etc/3node/ldap2apache
+
+=head1 REQUIREMENTS
+
+libxml-parser-perl, libxml-dumper-perl, libnet-ldap-perl, libhtml-template-perl
+
+=head1 SEE ALSO
+
+Dokumentation fuer Webhosting.
+
+=cut
+
+use strict;
+use Net::LDAP;
+use XML::Parser;
+use XML::Dumper;
+use HTML::Template;
+use Getopt::Long;
+use English;
+use Carp qw{cluck};
+
+$ENV{'PATH'} = '/bin:/usr/bin';
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+my $TEMP_DIR_MODE = 03775;
+my $TEMP_DIR_GROUP = getgrnam('www-data')
+ or die ("$PROGRAM_NAME: Cannot get gid of data");
+
+my $PROGNAME = $0;
+my $CONFIG;
+{
+ my $parser = new XML::Parser(Style => 'Tree');
+ my $tree = $parser->parsefile('/etc/3node/ldap2analog');
+ my $dump = new XML::Dumper;
+ $CONFIG = $dump->xml2pl($tree);
+}
+
+
+Getopt::Long::config('bundling');
+if (!GetOptions (
+ 'v' => \$CONFIG->{'verbose'},
+ 'h' => \$CONFIG->{'help'},
+ )) {
+ die ("Usage: $PROGNAME [-vshfn]\n");
+};
+if ($CONFIG->{'help'}) {
+ print ("Usage: $PROGNAME [-vshfn]\n");
+ exit 0;
+};
+
+
+
+
+my $ldap = Net::LDAP->new($CONFIG->{'ldapserver'}) or
+ die ("$PROGNAME: Cannot create LDAP object: $@");
+
+my $code;
+if ($CONFIG->{'binddn'} ne '') {
+ $code = $ldap->bind(
+ dn => $CONFIG->{'binddn'},
+ password => $CONFIG->{'bindpw'});
+} else {
+ $code = $ldap->bind();
+};
+die "$PROGNAME: can't connect to ldap server '$CONFIG->{'ldapserver'}': ".$code->error."\n" if ($code->code);
+
+
+$code = $ldap->search(
+ filter => "(objectclass=$CONFIG->{'client_objectclass'})",
+ base => $CONFIG->{'basedn'},
+ timelimit => $CONFIG->{'timeout'}
+);
+die "$PROGNAME: Problem to search '(objectclass=$CONFIG->{'client_objectclass'})' in $CONFIG->{'basedn'}: ".$code->error."\n" if ($code->code);
+
+
+my %FILELIST;
+my @entries = $code->entries;
+for my $client (@entries) {
+ $code = $ldap->search(
+ filter => "(&(objectclass=$CONFIG->{'webvhost_objectclass'})(tnHost=$CONFIG->{'thishost'}))",
+ base => $client->dn,
+ timelimit => $CONFIG->{'timeout'}
+ );
+ if ($code->code) {
+ warn "$PROGNAME: Problem to search '(&(objectclass=$CONFIG->{'webvhost_objectclass'})(tnHost=$CONFIG->{'thishost'}))' in $client->dn: ".$code->error."\n";
+ next;
+ };
+
+ my $home_base = $client->get_value('homeDirectory');
+ my @vhosts = $code->entries;
+ for my $entry (@vhosts) {
+ my $host;
+ # FIXME - check whether we serve this domain at this server
+
+ # *** ServerName
+ $host->{'ServerName'} = $entry->get_value('tnWebVHostServerName');
+
+ my $log_dir = $home_base . '/logs';
+ $log_dir =~ s,//+,/,g;
+
+ $host->{'CombinedLog'} = $log_dir.'/'.$host->{'ServerName'}.'-combined.log*';
+ $host->{'TransferLog'} = $log_dir.'/'.$host->{'ServerName'}.'-access.log*';
+ $host->{'RefererLog'} = $log_dir.'/'.$host->{'ServerName'}.'-referer.log*';
+ $host->{'AgentLog'} = $log_dir.'/'.$host->{'ServerName'}.'-agent.log*';
+
+ for my $tool (qw{analog rmagic}) {
+ printf STDERR "Doing template magic for $tool at host $host->{'ServerName'}.\n" if $CONFIG->{'verbose'};
+
+ my $tmpl = HTML::Template->new(
+ filename => $CONFIG->{$tool.'template'},
+ strict => 0,
+ die_on_bad_params => 0
+ );
+ $tmpl->param( %$host );
+ my $new_config = $tmpl->output();
+
+ my $filename = $CONFIG->{$tool.'confdir'}.'/'.$host->{'ServerName'};
+ open (F, '>'.$filename) or
+ die ("Cannot open $filename\n");
+ print F $new_config;
+ close (F);
+ };
+ $FILELIST{$host->{'ServerName'}} = 1;
+ };
+};
+
+
+
+
+# Get rid of old zonefiles
+for my $tool (qw{analog rmagic}) {
+ opendir(DIR, $CONFIG->{$tool.'confdir'}) || die ("$PROGNAME: Cannot open dir ".$CONFIG->{$tool.'confdir'}.": $!\n");
+ my @files = grep { ! /^\./ } readdir (DIR);
+ closedir(DIR);
+ @files = grep { ! $FILELIST{$_} } @files;
+ for my $file (@files) {
+ ($file) = $file =~ /(.*)/; #untaint
+ unlink ($CONFIG->{$tool.'confdir'}.'/'.$file) || warn ("Cannot unlink $file: $!\n");
+ };
+};
+
+$ldap->unbind;
diff --git a/bin/ldap2apache b/bin/ldap2apache
new file mode 100755
index 0000000..88fcfc7
--- /dev/null
+++ b/bin/ldap2apache
@@ -0,0 +1,236 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+
+@config = YAML::load( File.open( '/etc/noreply/config' ) )
+ldap = MyLDAP.new(@config, "ldap2apache")
+clients = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, 'objectclass=tnClient')
+# webstat
+@rmagicdir = @config['module']['analog']['rmagic']
+@webstat_location = @config['module']['analog']['webstat']
+
+@defaultbind = @config['module']['apache']['defaultbind']
+@defaultbindhttpport = @config['module']['apache']['defaultbindhttpport']
+@defaultbindhttpsport = @config['module']['apache']['defaultbindhttpsport']
+@configdir = @config['module']['apache']['configdir']
+@postgres_uid = @config['module']['apache']['postgres_uid']
+@www_gid = @config['module']['apache']['www_gid']
+@configtest = @config['module']['apache']['configtest']
+@reload = @config['module']['apache']['reload']
+
+files = {}
+
+def mkdir(dir, mode, uid, gid)
+ Dir.mkdir(dir, mode) unless File.exists?(dir)
+ # unless file is a symlink
+ unless FileTest.symlink?(dir)
+ File.chown(uid, gid, dir) == 1 or throw "chown #{dir} failed"
+ end
+end
+
+clients.each{ |c|
+ c['vhosts'] = ldap.conn.search2(c['dn'][0], LDAP::LDAP_SCOPE_SUBTREE, '(&(objectclass=tnWebVHost)(tnHost='+@config['thishost']+'))')
+ client_home = c['homeDirectory'][0] or throw "No home for #{d['dn'][0]}"
+ uid = c['uidNumber'][0].to_i
+ gid = c['gidNumber'][0].to_i
+ mkdir(client_home , 02755, 0, 0)
+ mkdir(client_home+"/logs" , 02750, 0, gid)
+ mkdir(client_home+"/logs-archive" , 02750, 0, gid)
+ mkdir(client_home+"/logs-for-stat", 02750, 0, gid)
+ mkdir(client_home+"/pg" , 02750, @postgres_uid, gid)
+ c['vhosts'].each{ |vhost|
+ bind = vhost['tnWebVHostBind'] ? vhost['tnWebVHostBind'][0] : @defaultbind
+ bindhttpport = vhost['tnWebVHostBindHTTPPort'] ? vhost['tnWebVHostBindHTTPPort'][0] : @defaultbindhttpport
+ bindhttpsport = vhost['tnWebVHostBindHTTPSPort'] ? vhost['tnWebVHostBindHTTPSPort'][0] : @defaultbindhttpsport
+ server_name = vhost['tnWebVHostServerName'][0]
+ server_admin = vhost['tnWebVHostWebmaster'][0]
+ server_aliases = (vhost['tnWebVHostServerAlias'] or []).join(" ")
+ home = client_home +"/"+ vhost['tnWebVHostHomeDirectory'][0]
+ property = {}
+ if vhost['tnWebVHostProperties']
+ vhost['tnWebVHostProperties'].each{ |prop|
+ (key, val) = prop.split('=', 2)
+ property[key] = val
+ }
+ end
+ property['php'] = "no" unless property['php'] == "yes"
+
+ dirindex = vhost['tnWebVHostDirectoryIndex'][0] if vhost['tnWebVHostDirectoryIndex']
+
+ docdiroptions = (vhost['tnWebVHostDocDirOptions'] or []).join(" ")
+ cgidiroptions = (vhost['tnWebVHostCgiDirOptions'] or []).join(" ")
+
+ docdiroptions += " Includes" if property['ssi'] == "yes"
+
+ addto = vhost['tnWebVHostAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostAddto']
+ docdiraddto = vhost['tnWebVHostDocDirAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostDocDirAddto']
+ cgidiraddto = vhost['tnWebVHostCgiDirAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostCgiDirAddto']
+
+ ssl = true if property['https'] == "only"
+
+ umask=File.umask
+ File.umask(0000)
+ mkdir(home, 02755, uid, gid)
+ mkdir(home+"/htdocs", 02755, uid, gid)
+ mkdir(home+"/bin", 02755, 0, gid)
+ mkdir(home+"/cgi-bin", 02755, uid, gid) if property['cgi-bin'] == "yes"
+ mkdir(home+"/tmp", 03775, uid, @www_gid)
+ File.umask(umask)
+
+ basedir = "#{home}"
+ if property['open_basedir']
+ if property['open_basedir'] != "none"
+ basedir += ":" + property['open_basedir']
+ else
+ basedir = nil
+ end
+ end
+ if property['safemode_include']
+ safemode_include = property['safemode_include']
+ end
+ config = []
+ if ssl
+ crtfile = "/etc/ssl/certs/apache-#{server_name}.pem"
+ keyfile = "/etc/ssl/private/apache-#{server_name}.key"
+ STDERR.puts "Warning: #{crtfile} does not exist" unless FileTest.exists?(crtfile)
+ STDERR.puts "Warning: #{keyfile} does not exist" unless FileTest.exists?(keyfile)
+
+ config << "<VirtualHost #{bind}:#{bindhttpsport}>"
+ config << " SSLEngine on"
+ config << " SSLCertificateFile #{crtfile}"
+ config << " SSLCertificateKeyFile #{keyfile}"
+ config << ' <Files ~ "\.php$">'
+ config << ' SSLOptions +StdEnvVars'
+ config << ' </Files>'
+ config << ' SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown'
+ config << ''
+ else
+ config << "<VirtualHost #{bind}:#{bindhttpport}>"
+ end
+ config << " ServerName #{server_name}"
+ config << " ServerAlias #{server_aliases}" if server_aliases != ""
+ config << " ServerAdmin #{server_admin}"
+ config << ""
+ config << " UserDir disabled" unless property['userdir'] == "yes"
+ config << " SuexecUserGroup #{c['uid']} #{c['uid']}" unless property['setusergroup'] == "no"
+ #config << " User #{c['uid']}" unless property['setusergroup'] == "no"
+ #config << " Group #{c['uid']}" unless property['setusergroup'] == "no"
+ config << " ErrorLog #{client_home}/logs/#{server_name}-error.log"
+ config << " LogLevel warn"
+ config << " CustomLog #{client_home}/logs/#{server_name}-access.log combined"
+ config << " ServerSignature On"
+ config << ""
+ # webstats
+ config << " Alias " + @webstat_location + " " + @rmagicdir + "/" + server_name + "/"
+ config << ""
+ unless property['php'] == "no"
+ config << " php_admin_value engine 1"
+ unless property['safe_mode'] == "no"
+ config << " php_admin_value safe_mode 1"
+ config << " php_admin_value safe_mode_gid 1"
+ config << " php_admin_value safe_mode_exec_dir \"#{home}/bin\""
+ end
+ config << " php_admin_value upload_tmp_dir \"#{home}/tmp\""
+ config << " php_admin_value sendmail_path \"/usr/sbin/sendmail -t -i -f #{server_admin}\""
+ config << " php_admin_value allow_url_fopen 0"
+ else
+ config << " php_admin_value engine off"
+ config << " AddType text/plain .php3"
+ config << " AddType text/plain .php3s"
+ config << " AddType text/plain .php"
+ config << " AddType text/plain .php4"
+ config << " AddType text/plain .phps"
+ end
+ unless property['document_root'] == "manual"
+ config << " DocumentRoot #{home}/htdocs"
+ end
+ config << "# <Directory #{home}/htdocs>"
+ config << " <Location />"
+ config << " AllowOverride FileInfo AuthConfig Limit Indexes Options"
+ config << " IndexOptions FancyIndexing NameWidth=*"
+ unless property['php'] == "no"
+ config << " php_value magic_quotes_gpc 0"
+ config << " php_admin_value open_basedir \"#{basedir}\"" if basedir
+ config << " php_admin_value safe_mode_include_dir \"#{safemode_include}\"" if safemode_include
+ config << " php_value include_path \".:#{home}/include:/usr/share/php:/usr/share/pear\"" if basedir
+ end
+ config << " Options #{docdiroptions}" if docdiroptions != ""
+ config << " #{docdiraddto}" if docdiraddto
+ config << " DirectoryIndex #{dirindex}" if dirindex
+ config << " AddHandler server-parsed .html" if property['ssi'] == 'yes'
+ config << " AddType text/html .shtml" if property['ssi'] == 'yes'
+ config << " AddHandler server-parsed .shtml" if property['ssi'] == 'yes'
+ config << " </Location>"
+ config << "# </Directory>"
+ if property['cgi-bin'] == "yes"
+ cgihome = home.gsub(/^\/srv\/www\/vhosts/, '/var/www/vhosts')
+ config << " ScriptAlias /cgi-bin #{cgihome}/cgi-bin"
+ config << " <Directory #{cgihome}/cgi-bin>"
+ config << " AllowOverride FileInfo AuthConfig Limit Indexes"
+ config << " Options ExecCGI #{cgidiroptions}"
+ config << " #{cgidiraddto}" if cgidiraddto
+ config << " </Directory>"
+ end
+ config << " #{addto}" if addto
+ config << "</VirtualHost>"
+ config << '# vim:ft=apache:'
+ throw "Clash on #{server_name} of client#{c['o'][0]}" if files[ c['o'][0] +"-"+ server_name ]
+ files[ c['o'][0] +"-"+ server_name ] = config
+
+ if ssl
+ config = []
+ config << "<VirtualHost #{bind}:#{bindhttpport}>"
+ config << " ServerName #{server_name}"
+ config << " ServerAlias #{server_aliases}" if server_aliases != ""
+ config << " ServerAdmin #{server_admin}"
+ config << " UserDir disabled"
+ config << " ErrorLog #{client_home}/logs/#{server_name}-error.log"
+ config << " LogLevel warn"
+ config << " CustomLog #{client_home}/logs/#{server_name}-access.log combined"
+ config << " ServerSignature On"
+ config << " RewriteEngine on"
+ config << " RewriteRule ^/(.*)$ https://#{server_name}/$1 [L,R]"
+ config << "</VirtualHost>"
+ config << '# vim:ft=apache:'
+
+ server_name += '-redir'
+ throw "Clash on #{server_name} of client#{c['o'][0]}" if files[ c['o'][0] +"-"+ server_name ]
+ files[ c['o'][0] +"-"+ server_name ] = config
+ end
+ }
+}
+
+need_reload = false
+Dir.entries( @configdir ).each{ |e|
+ next if ((e =~ /^\./) != nil)
+ next if files.has_key?( e )
+ File.unlink( @configdir + '/' + e )
+ need_reload = true
+}
+
+files.each_pair{ |name, config|
+ filename = @configdir + '/' + name
+ conf = config.join("\n") + "\n"
+ on_disk = FileTest.exists?(filename) ? File.read(filename) : nil
+ next if on_disk == conf
+
+ f = File.new( filename, "w" )
+ f.write(conf)
+ f.close
+
+ need_reload = true
+}
+
+if need_reload
+ system(@configtest) or throw "Configtest returned errors."
+ system(@reload) or throw "Reload returned errors."
+end
diff --git a/bin/ldap2bind b/bin/ldap2bind
new file mode 100755
index 0000000..139a4b0
--- /dev/null
+++ b/bin/ldap2bind
@@ -0,0 +1,344 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require "optparse"
+
+# defaults
+@force_push = false
+@verbose = false
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-f", "--force" , String, "Force Push to Slaves") { @force_push = true }
+ opts.on("-v", "--verbose" , String, "Show SSH-Transfer more verbose") { @verbose = true }
+ opts.parse!
+end
+
+def fatal(reason)
+ STDERR.puts reason
+ exit 1;
+end
+
+def getOldSerial(name)
+ filename = @baseDir+'/'+name
+ return nil unless FileTest.exists?(filename)
+ File.open(filename).readlines.each{ |line|
+ match = /^; serial: (\d+)/.match(line)
+ next unless match
+ serial = match[1]
+ return serial.to_i
+ }
+ return nil
+end
+
+def makeNewSerial(oldSerial)
+ serial = Time.now.strftime("%Y%m%d00").to_i
+ if oldSerial
+ serial = (oldSerial + 1) if oldSerial >= serial
+ end
+ return serial
+end
+
+def setRecord(domains, type, rrdata, dn)
+ domains.each{ |d|
+ handled = false
+ domainparts = d.split('.')
+ localpart = []
+ while domainparts.length > 0
+ if @zones.has_key?(domainparts.join('.'))
+ left = localpart.length > 0 ? localpart.join('.') : '@'
+ right = domainparts.join('.')
+ @zones[ right ]['records'][left] = {} unless @zones[ right ]['records'][left]
+ if @zones[ right ]['records'][left].has_key?(type)
+ throw "There already are #{type} records for #{left} in #{right} (dn=#{dn}"
+ end
+ @zones[ right ]['records'][left][type] = [ rrdata ]
+ handled = true
+ break
+ end
+ localpart << domainparts.shift
+ end
+ # FIXME
+ #STDERR.puts "Warning: Did not find a place to put #{d}'s #{type} records" unless handled
+ }
+end
+
+def setMXs(domains, hosts, dn)
+ throw "Cannot handle mail entry for more than 1 host (dn=#{dn})" if hosts.length != 1
+ host = hosts[0]
+ rrdata = @rrdata['MX'][host]
+ throw "Do not know MX data for host #{host} (dn=#{dn})" unless rrdata
+ setRecord(domains, 'MX', rrdata, dn);
+end
+
+def setAs(domains, hosts, dn)
+ throw "Cannot handle A entry for more than 1 host (dn=#{dn})" if hosts.length != 1
+ host = hosts[0]
+ rrdata = @rrdata['A'][host]
+ throw "Do not know A data for host #{host} (dn=#{dn})" unless rrdata
+ setRecord(domains, 'A', rrdata, dn);
+end
+
+def setAsDirect(domains, ipaddress, dn)
+ throw "Cannot handle A entry for more than 1 ipaddress (dn=#{dn})" if ipaddress.length != 1
+ setRecord(domains, 'A', ipaddress, dn);
+end
+
+
+
+@config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+@defaultSoaPerson = @config['module']['bind']['soaPerson']
+@defaultTTL = @config['module']['bind']['ttl']
+@defaultNameservers = @config['module']['bind']['nameservers']
+@baseDir = @config['module']['bind']['baseDir']
+@namedconf = @config['module']['bind']['namedconf']
+@pushedList = @config['module']['bind']['pushedList']
+@reload = @config['module']['bind']['reload']
+@rrdata = @config['module']['bind']['rrdata']
+@Origin = @config['module']['bind']['thisNameserver']
+@nameserverIPmapping = @config['module']['bind']['nameserverIPmapping']
+@TSIGslaves = @config['module']['bind']['TSIGslaves']
+@pushToServers = @config['module']['bind']['pushToServers']
+@SSHidentityFile = @config['module']['bind']['SSHidentityFile']
+
+ldap = MyLDAP.new(@config, "ldap2bind")
+magicItems = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, '(|(tnMagicDNS=yes)(objectClass=tnDNSrr))')
+masterDomains = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, 'objectclass=tnDNSsoa')
+slaveDomains = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, 'objectclass=tnDNSsecondary')
+
+@zones = {}
+@secondaryzonesUnique = {}
+@secondaryzones = []
+
+masterDomains.each { |d|
+ domain = {}
+ throw "Domain starts with dot: #{d['tnDNSdomainname'][0]}" if ((d['tnDNSdomainname'][0] =~ /^\./) != nil)
+ domain['soaPerson'] = d['tnDNSsoaPerson'] ? d['tnDNSsoaPerson'][0] : @defaultSoaPerson
+ domain['ttl'] = d['tnDNSttl'] ? d['tnDNSttl'][0] : @defaultTTL
+
+ domain['records'] = {}
+ domain['records']['@'] = {}
+ domain['records']['@']['NS'] = d['tnDNSnameservers'] ? d['tnDNSnameservers'] : @defaultNameservers
+
+ name = d['tnDNSdomainname'][0]
+ if @zones.has_key?( name )
+ STDERR.puts "Warning: Domain #{name} has multiple ldap entries in master!"
+ end
+ @zones[ name ] = domain
+
+ @secondaryzonesUnique[name] = 1
+ slaveentry = {}
+ slaveentry['name'] = name
+ slaveentry['master'] = [ @nameserverIPmapping[@Origin] ]
+ slaveentry['slaves'] = domain['records']['@']['NS'].reject{|e| e == @Origin}
+ @secondaryzones << slaveentry
+}
+slaveDomains.each { |d|
+ name = d['tnDNSdomainname'][0]
+ if @secondaryzonesUnique.has_key?( name )
+# STDERR.puts "Warning: Domain #{name} has multiple ldap entries as slaves!"
+ end
+ @secondaryzonesUnique[name] = 1
+ slaveentry = {}
+ slaveentry['name'] = name
+ slaveentry['master'] = d['tnDNSprimary']
+ slaveentry['slaves'] = d['tnDNSnameservers']
+ @secondaryzones << slaveentry
+ d['tnDNSnameservers'].each{ |secondary|
+ if not @nameserverIPmapping.has_key?(secondary)
+ STDERR.puts "Warning: Unknown nameserver #{secondary} for domain #{name}."
+ next
+ end
+ }
+}
+
+magicItems.each { |m|
+ recognized_one = false
+ m['objectClass'].each{ |objectclass|
+ case objectclass
+ when 'top', 'tnMailRemotePerson'
+ when 'tnMailDomain', 'tnMailRelay', 'tnUUCPSystem'
+ setMXs(m['tnMailDomainname'], m['tnHost'], m['dn'][0])
+ when 'tnWebVHost'
+ setAs(m['tnWebVHostServerName'], m['tnHost'], m['dn'][0])
+ if m['tnWebVHostServerAlias']
+ m['tnWebVHostServerAlias'].each{ |vhostalias|
+ setAs(vhostalias, m['tnHost'], m['dn'][0])
+ }
+ end
+ when 'tnDNSrr'
+ if m['tnDNSaRecord']
+ setAsDirect(m['tnDNSdomainname'], m['tnDNSaRecord'], m['dn'][0])
+ else
+ STDERR.puts "Warning: Unhandled tnDNSrr: #{m['dn'][0]}."
+ end
+ else
+ STDERR.puts "Warning: objectClass #{objectclass} in #{m['dn'][0]} not recognized."
+ end
+ }
+
+}
+
+# clean up
+Dir.entries( @baseDir ).each{ |e|
+ next if ((e =~ /^\./) != nil)
+ File.unlink( @baseDir + '/' + e ) unless @zones.has_key?( e )
+}
+
+zonelist = []
+zonelist << "// --------------------------------------------------"
+zonelist << "// -- --"
+zonelist << "// -- " + "master zones from ldap".center(44)+ " --"
+zonelist << "// -- --"
+zonelist << "// -- this file has been automatically created by --"
+zonelist << "// -- ldap2bind on "+Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")+" -- IGNORE_LINE"
+zonelist << "// --------------------------------------------------"
+zonelist << ""
+reload_required = false
+@zones.keys.sort.each{ |name|
+ domain = @zones[name]
+
+ zonelist << "zone \"#{name}\" {"
+ zonelist << " type master;"
+ zonelist << " file \"#{@baseDir}/#{name}\";"
+ zonelist << " allow-query { any; };"
+ zonelist << " allow-transfer {"
+ have_secondaries = false
+ domain['records']['@']['NS'].reject{|e| e == @Origin}.each{ |secondary|
+ if @TSIGslaves.has_key?(secondary)
+ zonelist << " key #{@TSIGslaves[secondary]}; // #{secondary}"
+ else
+ if not @nameserverIPmapping.has_key?(secondary)
+ STDERR.puts "Warning: Unknown nameserver #{secondary} in zone #{name}: cannot grant zone transfer"
+ next
+ end
+ ip = @nameserverIPmapping[secondary]
+ zonelist << " #{ip}; // #{secondary}"
+ zonelist << " ::ffff:#{ip}/128; // #{secondary}"
+ end
+ have_secondaries = true
+ }
+ zonelist << " \"none\";" unless have_secondaries
+ zonelist << " };"
+ zonelist << "};"
+ zonelist << ""
+
+
+ serial = makeNewSerial(getOldSerial(name))
+ zone = []
+ zone << "; --------------------------------------------------"
+ zone << "; -- --"
+ zone << "; -- " + name.center(44)+ " --"
+ zone << "; -- --"
+ zone << "; -- this file has been automatically created by --"
+ zone << "; -- ldap2bind on "+Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")+" -- IGNORE_LINE"
+ zone << "; --------------------------------------------------"
+ zone << "; serial: "+serial.to_s+" IGNORE_LINE"
+ zone << ";"
+ zone << "$TTL #{domain['ttl']}"
+ zone << "@ #{domain['ttl']} IN SOA #{ @Origin } #{ domain['soaPerson'] } ("
+ zone << " #{ serial } ; IGNORE_LINE"
+ zone << " 3H 33m 5W 20m )" # or {domain['ttl']} instead of 20m"
+
+ # We need to sort here so we get the same order every time, so we can
+ # compare the result with the on disk copy
+ domain['records'].keys.sort.each{ |dom|
+ domain['records'][dom].keys.sort.each{ |type|
+ domain['records'][dom][type].sort.each{ |l|
+ zone << "#{dom} IN #{type} #{l}"
+ }
+ }
+ }
+ zone << "localhost IN A 127.0.0.1"
+ #zone << "localhost. IN A 127.0.0.1"
+ zone << ""
+ zone << "; vim:set syn=dns:"
+
+ zone.map!{ |line| line + "\n" }
+
+ if (FileTest.exists?(@baseDir + '/' + name))
+ on_disk = File.new( @baseDir + '/' + name, "r" ).readlines.reject{ |e| e =~ /IGNORE_LINE/ }
+ next if on_disk == zone.reject{ |e| e =~ /IGNORE_LINE/ }
+ end
+ f = File.new( @baseDir + '/' + name, "w" )
+ f.write(zone)
+ f.close
+ reload_required = true
+}
+pushingList = []
+@secondaryzones.sort{|x,y| x['name'] <=> y['name'] }.each{ |slaveentry|
+ if slaveentry['slaves'].include?(@Origin)
+ zonelist << "zone \"#{slaveentry['name']}\" {"
+ zonelist << " type slave;"
+ zonelist << " file \"slave-ldap-#{slaveentry['name']}\";"
+ zonelist << " allow-transfer { \"none\"; };"
+ zonelist << " masters {"
+ slaveentry['master'].each{ |master|
+ zonelist << " #{master};"
+ };
+ zonelist << " };"
+ zonelist << "};"
+ zonelist << ""
+ end
+ pushingList << slaveentry['name']+" "+slaveentry['master'].join('|')+" "+" "+slaveentry['slaves'].join(" ")
+}
+
+
+zonelist.map!{ |line| line + "\n" }
+
+if (FileTest.exists?(@namedconf))
+ on_disk = File.new(@namedconf).readlines.reject{ |e| e =~ /IGNORE_LINE/ }
+ reload_required = true unless on_disk == zonelist.reject{ |e| e =~ /IGNORE_LINE/ }
+else
+ reload_required = true
+end
+f = File.new( @namedconf, "w" )
+f.write(zonelist)
+f.close
+
+
+pushingList.map!{ |line| line + "\n" }
+# sometimes force push so set it per default to true
+push_required=@force_push
+
+if (FileTest.exists?(@pushedList))
+ on_disk = File.new(@pushedList).readlines.reject{ |e| e =~ /IGNORE_LINE/ }
+ push_required = true unless on_disk == pushingList.reject{ |e| e =~ /IGNORE_LINE/ }
+else
+ push_required = true
+end
+if push_required
+ f = File.new( @pushedList, "w" );
+ f.write(pushingList)
+ f.close
+ if @pushToServers
+ @pushToServers.each{ |server|
+ puts "Pushing to #{server}"
+ if @verbose
+ system("env -i ssh -4 -v -T -l root -i #{@SSHidentityFile} #{server} < #{@pushedList}") or throw "push to #{server} failed\n"
+ else
+ system("env -i ssh -4 -T -l root -i #{@SSHidentityFile} #{server} < #{@pushedList}") or throw "push to #{server} failed\n"
+ end
+ }
+ end
+end
+
+
+if (reload_required)
+ puts "Reloading bind."
+ system(@reload) or throw "#{@reload} failed\n";
+end
diff --git a/bin/ldap2chroot-accounts b/bin/ldap2chroot-accounts
new file mode 100755
index 0000000..2cdadab
--- /dev/null
+++ b/bin/ldap2chroot-accounts
@@ -0,0 +1,139 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+
+
+
+def fatal(reason)
+ STDERR.puts reason
+ exit 1;
+end
+
+# read mtab
+#
+# /dev/sda11 /aux xfs rw 0 0
+# /chroot-accounts/MasterChroot/bin /chroot-accounts/weasel.weasel/bin none rw,bind 0 0
+#
+def getmounts()
+ mounts = Hash.new
+
+ IO.foreach('/etc/mtab') { |line|
+ (device, mountpoint, fstype, option, dummy1, dummy2) = line.split
+ next unless mountpoint.index(@spool)
+
+ fatal("Unexpected option or fstype in "+line) if (!(option =~ /bind/) || (fstype != 'none'))
+
+ # check mountpoint in detail
+ components = mountpoint.gsub(/^\//, '').split('/');
+ fatal("Weird mountpoint (not 3 components) in "+line) unless (components.size == 3)
+ fatal("1st componend of mountpoint is not sane in "+line) unless ('/' + components[0] == @spool)
+ fatal("2nd componend of mountpoint is not sane in "+line) unless (components[1] =~ /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/)
+ fatal("3rd componend of mountpoint is not sane in "+line) unless (["bin", "usr", "lib", components[1]].include?(components[2]))
+
+ mounts[components[1]] = Hash.new unless mounts[components[1]]
+ mounts[components[1]][components[2]] = device
+ }
+ mounts
+end
+
+@config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+masterName = @config['module']['ftp']['masterName']
+@spool = (@config['module']['ftp']['chrootBaseDir']).gsub('//', '/')
+@spool.chop! if @spool =~ /\/$/
+@master = @spool + '/' + masterName + '/'
+
+ldap = MyLDAP.new(@config, "ldap2chroot-accounts")
+accounts = Hash.new
+ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE,
+ '(&(objectclass=tnFTPuser)(tnHost='+@config['thishost']+'))').each{ |e|
+ accounts[ e['uid'][0] ] = e
+}
+
+mounts = getmounts
+
+# create all account directories
+accounts.each_key{ |homedir|
+ dir = @spool + '/' + homedir
+ Dir.mkdir(dir) unless File.exists?(dir)
+}
+
+existingdirs = Dir.entries( @spool ).delete_if { |e| ((e =~ /^\./) != nil) }
+existingdirs.delete( masterName )
+# Iterate over all existing directories
+# checking up on mounts, and update them if necessary
+existingdirs.each { |homedir|
+ basedir = @spool + '/' + homedir
+ if accounts[homedir]
+ # Check bin, usr, and lib mounts
+ # also, the user's datadir is in a directory with the same name as her account
+ subdirs = %w(bin usr lib)
+ subdirs << homedir
+ subdirs.each { |subdir|
+ supposedLocation = subdir == homedir ? accounts[homedir]['tnFTPDataDirectory'][0] : @master + subdir
+ mountpoint = basedir + '/' + subdir
+
+ # umount if it's mounted in the wrong place
+ if mounts[homedir] && mounts[homedir][subdir]
+ if mounts[homedir][subdir] != supposedLocation
+ STDERR.puts("warn: Directory "+mountpoint+" is bind mounted to the wrong place: "+mounts[homedir][subdir]+". Should be "+supposedLocation+" remounting.")
+ system("umount " + mountpoint) or fatal("umount for "+mountpoint+" failed");
+ mounts[homedir].delete(subdir)
+ end
+ end
+
+ # mount if it's not mounted
+ if !(mounts[homedir] && mounts[homedir][subdir])
+ Dir.mkdir(mountpoint) unless File.exists?(mountpoint)
+ system("mount -o bind "+ supposedLocation + " " + mountpoint) or fatal("mount for "+mountpoint+" failed");
+ end
+ }
+ # create etc
+ etcdir = basedir + '/etc'
+ Dir.mkdir(etcdir, 0111) unless File.exists?(etcdir)
+ uid = accounts[homedir]['uid'][0]
+ uidNumber = accounts[homedir]['uidNumber'][0]
+ gidNumber = accounts[homedir]['gidNumber'][0]
+ passwd = "root:x:0:0:root::\n" + "%s:x:%d:%d:::\n"%[uid, uidNumber, gidNumber]
+ group = "root:x:0:\n" + "%s:x:%s:\n"%[uid, gidNumber]
+ File.open(etcdir+'/passwd', File::CREAT|File::TRUNC|File::RDWR, 0444).write(passwd)
+ File.open(etcdir+'/group', File::CREAT|File::TRUNC|File::RDWR, 0444).write(group)
+
+ # create .ssh
+ sshdir = basedir + '/.ssh'
+ Dir.mkdir(sshdir, 0111) unless File.exists?(sshdir)
+ authkeys = '';
+ accounts[homedir]['tnSSHKey'] and accounts[homedir]['tnSSHKey'].each{ |key|
+ key.gsub!('\n', '')
+ authkeys << "no-port-forwarding,no-X11-forwarding,no-agent-forwarding "+key+"\n"
+ }
+ File.open(sshdir+'/authorized_keys', File::CREAT|File::TRUNC|File::RDWR, 0444).write(authkeys)
+ else
+ # clean up old stuff
+ mounts[homedir] and mounts[homedir].each_key{ |subdir|
+ mountpoint = basedir + '/' + subdir
+ system("umount " + mountpoint) or fatal("umount for "+mountpoint+" failed");
+ Dir.rmdir(mountpoint)
+ }
+ { 'etc' => ['passwd', 'group'], '.ssh' => ['authorized_keys'], homedir => [] }.each{ |dir, files|
+ dir = basedir + '/' + dir
+ if File.exists?(dir)
+ files.each { |f|
+ file = dir + '/' + f
+ File.unlink(file) if File.exists?(file)
+ }
+ Dir.rmdir(dir)
+ end
+ }
+ Dir.rmdir(basedir)
+ end
+}
diff --git a/bin/ldap2mail b/bin/ldap2mail
new file mode 100755
index 0000000..71eddd9
--- /dev/null
+++ b/bin/ldap2mail
@@ -0,0 +1,29 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+
+
+@config = YAML::load( File.open( '/etc/noreply/config' ) )
+@basedir = @config['module']['mail']['basedir']
+
+
+ldap = MyLDAP.new(@config, "ldap2mail")
+clients = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, 'objectclass=tnClient')
+
+clients.each { |c|
+ if not File.exists?(@basedir + "/" + c['o'][0])
+ Dir.mkdir(@basedir + "/" + c['o'][0], 02700 )
+ File.chown(c['uidNumber'][0].to_i, c['gidNumber'][0].to_i, @basedir + "/" + c['o'][0])
+ elsif not File.stat( @basedir + "/" + c['o'][0]).directory?
+ STDERR.puts("Warning: #{@basedir}/#{c['o'][0]} is not a directory.");
+ end
+}
diff --git a/bin/ldap2passwd b/bin/ldap2passwd
new file mode 100755
index 0000000..0e325b3
--- /dev/null
+++ b/bin/ldap2passwd
@@ -0,0 +1,94 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "yaml"
+
+@config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+VALIDATION_REGEX = {
+ 'uid' => /^[Wa-z.-]+$/,
+ 'cn' => /^[Wa-z-]+$/,
+ 'uidNumber' => /^[1-9][0-9]+$/,
+ 'gidNumber' => /^[1-9][0-9]+$/,
+ 'gecos' => /^[a-zA-Z0-9,. -]+$/,
+ 'homeDirectory' => /^\/[a-zA-Z0-9.\/-]+$/,
+ 'loginShell' => /^\/[a-zA-Z0-9\/-]+$/
+}
+
+
+5.times{
+ begin
+ @conn = LDAP::Conn.new(@config['ldapserver'], @config['ldapport'] )
+ break if @conn
+ rescue LDAP::ResultError
+ sleep 3
+ end
+};
+unless @conn.bind(@config['credentials']['ldap2passwd']['binddn'],
+ @config['credentials']['ldap2passwd']['bindpw'])
+ throw @conn.perror("bind")
+end
+
+def passwd_line(entry)
+ l = {}
+ %w(uid uidNumber gidNumber gecos homeDirectory loginShell).each{ |key|
+ next if ['gecos', 'loginShell'].include?(key) and not entry[key]
+ throw "key #{key} missing in #{entry['dn']}" unless entry[key]
+ throw "no validation regex for #{key}" unless VALIDATION_REGEX[key]
+
+ l[key] = entry[key].to_s
+ throw "value for #{key} (#{l[key]}) in #{entry['dn']} fails validation regex #{VALIDATION_REGEX[key]}" unless l[key] =~ VALIDATION_REGEX[key]
+ };
+ l['gecos'] = '' unless l['gecos']
+ l['loginShell'] = '/bin/false' unless l['loginShell']
+ sprintf "%s:x:%s:%s:%s:%s:%s\n", l['uid'], l['uidNumber'], l['gidNumber'], l['gecos'], l['homeDirectory'], l['loginShell']
+end
+def group_line(entry)
+ l = {}
+ %w(cn gidNumber).each{ |key|
+ throw "key #{key} missing in #{entry['dn']}" unless entry[key]
+ throw "no validation regex for #{key}" unless VALIDATION_REGEX[key]
+
+ l[key] = entry[key].to_s
+ throw "value for #{key} (#{l[key]}) in #{entry['dn']} fails validation regex #{VALIDATION_REGEX[key]}" unless l[key] =~ VALIDATION_REGEX[key]
+ };
+ members = []
+ if entry['memberUid']
+ entry['memberUid'].each{ |member|
+ throw "empty member value in group #{entry['dn']}" unless member
+ m = member.to_s
+ throw "member of group #{entry['dn']} fails UID validiation test" unless m =~ VALIDATION_REGEX['uid']
+ members << m
+ }
+ end
+ sprintf "%s:x:%s:%s\n", l['cn'], l['gidNumber'], members.join(',')
+end
+
+
+passwd_lines = []
+passwd = @conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, '(objectClass=posixAccount)')
+passwd.each{ |line|
+ passwd_lines << passwd_line(line)
+}
+
+group_lines = []
+group = @conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, '(objectClass=posixGroup)')
+group.each{ |line|
+ group_lines << group_line(line)
+}
+
+unless File.exists?('passwd') && (File.read('passwd') == passwd_lines.join())
+ f = File.new('passwd', 'w')
+ f.print passwd_lines.join
+end
+
+unless File.exists?('group') && (File.read('group') == group_lines.join())
+ f = File.new('group', 'w')
+ f.print group_lines.join
+end
diff --git a/bin/ldap2postgres b/bin/ldap2postgres
new file mode 100755
index 0000000..4e26fb1
--- /dev/null
+++ b/bin/ldap2postgres
@@ -0,0 +1,138 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "myldap"
+require "yaml"
+require 'postgres'
+require 'optparse'
+
+
+$VERBOSE = nil
+
+def show_help(parser, code=0, io=STDOUT)
+ io.puts parser
+ exit(code)
+end
+ARGV.options do |opts|
+ opts.on_tail("-h", "--help", "Display this help screen") { show_help(opts) }
+ opts.on("-v", "--verbose" , nil, "Be verbose") { $VERBOSE = 1 }
+ opts.parse!
+end
+
+def fatal(reason)
+ STDERR.puts reason
+ exit 1;
+end
+
+def query(pg, query)
+ res = pg.exec(query)
+ if (res.status != PGresult::TUPLES_OK)
+ raise PGerror,"FETCH ALL command didn't return tuples properly\n"
+ end
+ r = res.result.collect { |tupl|
+ line = Hash.new
+ i = 0
+ tupl.each { |fld|
+ line[ res.fields[i] ] = fld
+ i = i +1
+ }
+ line
+ }
+ res.clear
+ r
+end
+
+@config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+ldap = MyLDAP.new(@config, "ldap2postgres")
+ldap_databases = Hash.new
+ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, '(objectclass=tnPostgreSQLdatabase)').each{ |e|
+ cn = e['cn'][0]
+ dn = e['dn'][0]
+ ldap_databases[ cn ] = Array.new
+ ldap.conn.search2(dn, LDAP::LDAP_SCOPE_SUBTREE, '(objectclass=tnPostgreSQLuser)').each{ |e2|
+ uid = e2['uid'][0]
+ if (uid.index(cn+"_") != 0)
+ STDERR.puts "User %s in dn: %s has weird name. Supposed to start with '%s_'. Ignoring user."%[uid, dn, cn] unless
+ dn == 'ou=postgresql,o=system,ou=hosting,%s'%[config['basedn']]
+ else
+ ldap_databases[ e['cn'][0] ] << uid
+ end
+ }
+}
+
+pg = PGconn.connect('', 5432, '', '', 'template1')
+pg_databases = query(pg, 'SELECT datname FROM pg_catalog.pg_database').collect { |line| line['datname'] }
+pg_users = query(pg, 'SELECT usename FROM pg_catalog.pg_user') .collect { |line| line['usename'] }
+pg_groups = query(pg, 'SELECT groname FROM pg_catalog.pg_group') .collect { |line| line['groname'] }
+
+
+all_databases = ldap_databases.keys + pg_databases
+all_databases.uniq!
+
+all_databases.each { |dbname|
+ if (! ldap_databases.has_key?(dbname) )
+ puts "Additional database in Postgers: "+dbname unless %w(template0 template1 ogo).include?(dbname)
+ next
+ elsif (! pg_databases.include?(dbname))
+ puts "Creating database and group: "+dbname
+ pg.exec('CREATE DATABASE "'+dbname+'"')
+ pg.exec('REVOKE ALL ON DATABASE "'+dbname+'" FROM PUBLIC')
+ else
+ puts 'Common database: "'+dbname+'"' if $VERBOSE
+ end
+ if (pg_groups.include?(dbname))
+ puts ' Common group: '+dbname if $VERBOSE
+ else
+ puts ' Creating group: '+dbname
+ pgdb = PGconn.connect('', 5432, '', '', dbname)
+ pgdb.exec('CREATE GROUP "'+dbname+'"')
+ pgdb.exec('REVOKE ALL ON SCHEMA public FROM PUBLIC')
+ pgdb.exec('GRANT ALL ON SCHEMA public TO GROUP "'+dbname+'"')
+ pgdb.close
+ end
+ pg_groups.delete(dbname)
+ pgdb = PGconn.connect('', 5432, '', '', dbname)
+ pg_schemas = query(pgdb, 'SELECT nspname FROM pg_catalog.pg_namespace') .collect { |line| line['nspname'] }
+
+ ldap_databases[dbname].each { |uid|
+ if (pg_users.include?(uid))
+ puts " Common user: "+uid if $VERBOSE
+ # this does not hurt if the user already is in the group, but if the
+ # group was droped for some reason and just created now, it readds
+ # the user to gthe group
+ pg.exec('ALTER GROUP "'+dbname+'" ADD USER "'+uid+'"')
+ else
+ puts " Adding user: "+uid
+ pg.exec('CREATE USER "'+uid+'" NOCREATEDB NOCREATEUSER IN GROUP "'+dbname+'"')
+ end
+
+ if (pg_schemas.include?(uid))
+ puts ' Common schema: '+uid if $VERBOSE
+ else
+ puts ' Adding schema '+uid
+ pgdb.exec('CREATE SCHEMA "'+uid+'" AUTHORIZATION "' +uid+'"')
+ pgdb.exec('REVOKE ALL ON SCHEMA "'+uid+'" FROM PUBLIC')
+ end
+ pg_schemas.delete(uid)
+ pg_users.delete(uid)
+ }
+ pg_schemas.each { |schemaname|
+ puts "Additional schemas: "+schemaname unless %w(pg_toast pg_temp_1 pg_catalog public information_schema).include?(schemaname)
+ }
+ pgdb.close
+}
+pg.close
+
+pg_users.each { |uid|
+ puts "Additional user: "+uid unless %w(postgres ogo).include?(uid)
+}
+pg_groups.each { |gid|
+ puts "Additional group: "+gid
+}
diff --git a/bin/ldap2uucp b/bin/ldap2uucp
new file mode 100755
index 0000000..5dbf8f5
--- /dev/null
+++ b/bin/ldap2uucp
@@ -0,0 +1,99 @@
+#!/usr/bin/ruby
+
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+require "ldap"
+require "getoptlong"
+require "myldap"
+require "yaml"
+require "html/template"
+
+config = YAML::load( File.open( '/etc/noreply/config' ) )
+
+ldap = MyLDAP.new(config, "ldap2uucp")
+
+systems = []
+
+begin
+ ldap.conn.search(config['basedn'], LDAP::LDAP_SCOPE_SUBTREE,
+ '(&(objectclass=tnUUCPSystem)'+
+ '(tnHost='+config['thishost']+'))') {|e|
+
+ systemname = e.vals("tnUUCPSysName").pop;
+ sshkey = e.vals("tnSSHKey").pop; # FIXME: only first used
+ password = e.vals("tnUUCPPassword").pop
+
+ systems.push( {
+ 'system' => systemname,
+ 'password' => password,
+ 'sshkey' => sshkey } )
+ }
+rescue LDAP::ResultError => msg
+ $stderr.print(msg)
+ exit 1;
+end
+
+
+spool = "/var/spool/uucp"
+existingdirs = Dir.entries( spool ).delete_if { |e| ((e =~ /^\./) != nil) || !File.stat( spool + '/' + e ).directory?}
+systems.each{ |s| existingdirs.delete( s['system'] ) };
+STDERR.puts "The following orphaned nodes have dirs in the uucp spool: " + existingdirs.join(", ") unless (existingdirs.empty?)
+
+#
+# /etc/uucp/passwd
+#
+templ = HTML::Template.new
+templ.set_html(<<EOF
+# begin autogenerated stuff
+<!begin:systems><!var:system> <!var:password>
+<!end:systems>
+# end autogenerated stuff
+EOF
+)
+templ.param({ 'systems' => systems })
+File.open("/etc/uucp/passwd", "w").write( templ.output );
+
+
+#
+# /etc/uucp/sys
+#
+templ = HTML::Template.new
+templ.set_html(<<EOF
+# begin autogenerated stuff
+protocol gvG
+protocol-parameter G packet-size 1024
+# protocol-parameter G window 7
+protocol-parameter G short-packets
+
+<!begin:systems>
+system <!var:system>
+ time any
+ called-login <!var:system>
+ protocol it
+
+<!end:systems>
+# end autogenerated stuff
+EOF
+)
+templ.param({ 'systems' => systems })
+File.open("/etc/uucp/sys", "w").write( templ.output );
+
+#
+# ~uucp/.ssh/authorized_keys
+#
+templ = HTML::Template.new
+templ.set_html(<<EOF
+# begin autogenerated stuff
+<!begin:systems>
+# <!var:system>
+no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/sbin/uucico -D -l" <!var:sshkey>
+<!end:systems>
+# end autogenerated stuff
+EOF
+)
+templ.param({ 'systems' => systems })
+File.open("/var/spool/uucp/.ssh/authorized_keys", "w").write( templ.output );
diff --git a/schema/3node2.schema b/schema/3node2.schema
new file mode 100644
index 0000000..7c2a413
--- /dev/null
+++ b/schema/3node2.schema
@@ -0,0 +1,943 @@
+# $Id: 3node2.schema 255 2005-01-12 03:09:06Z root $
+
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+
+# zu Syntaxdefinitionen siehe u.A. http://www.openldap.org/doc/admin/schema.html
+
+# OIDs sind wichtig fuer die Eindeutigkeit. Ich (Peter) habe von der IANA folgenden
+# Subtree zugewiesen bekommen: 1.3.6.1.4.1.12771
+# Unter diesem habe ich 1.3.6.1.4.1.12771.1 an die 3node delegiert.
+#
+# Ich verwalte eine Liste aller OIDs, vor Aenderungen unbedingt fragen!
+
+# 1.3.6.1.4.1.12771.4 3node v2
+# 1.3.6.1.4.1.12771.4.1 LDAP
+# 1.3.6.1.4.1.12771.4.1.1 Attributes
+# 1.3.6.1.4.1.12771.4.1.1.1 tnMailDomainname
+# 1.3.6.1.4.1.12771.4.1.1.2 tnHost
+# 1.3.6.1.4.1.12771.4.1.1.3 tnMailMailboxLimit
+# 1.3.6.1.4.1.12771.4.1.1.4 tnMailRemoteAddress
+# 1.3.6.1.4.1.12771.4.1.1.5 tnMailLocalAddress
+# 1.3.6.1.4.1.12771.4.1.1.6 tnMailRouting
+# 1.3.6.1.4.1.12771.4.1.1.7 tnMailMailboxHomedir
+# 1.3.6.1.4.1.12771.4.1.1.8 tnMailMailboxLocation
+# 1.3.6.1.4.1.12771.4.1.1.9 tnMailMailboxUID
+# 1.3.6.1.4.1.12771.4.1.1.10 tnMailMailboxGID
+# 1.3.6.1.4.1.12771.4.1.1.11 tnMailMailboxAccountname
+# 1.3.6.1.4.1.12771.4.1.1.12 tnMailVirtualAddress
+# 1.3.6.1.4.1.12771.4.1.1.13 tnMailAliasedAddress
+# 1.3.6.1.4.1.12771.4.1.1.14 tnMagicDNS
+# 1.3.6.1.4.1.12771.4.1.1.15 tnSSHKey
+# 1.3.6.1.4.1.12771.4.1.1.16 tnUUCPPassword
+# 1.3.6.1.4.1.12771.4.1.1.17 tnUUCPSysName
+# 1.3.6.1.4.1.12771.4.1.1.18 tnMailTransportDestination
+
+# 1.3.6.1.4.1.12771.4.1.1.101 tnWebVHostServerName
+# 1.3.6.1.4.1.12771.4.1.1.102 tnWebVHostHomeDirectory
+# 1.3.6.1.4.1.12771.4.1.1.103 tnWebVHostProperties
+# 1.3.6.1.4.1.12771.4.1.1.104 tnWebVHostDirectoryIndex
+# 1.3.6.1.4.1.12771.4.1.1.105 tnWebVHostServerAlias
+# 1.3.6.1.4.1.12771.4.1.1.106 tnWebVHostAddto
+# 1.3.6.1.4.1.12771.4.1.1.107 tnWebVHostBind
+# 1.3.6.1.4.1.12771.4.1.1.108 tnWebVHostDocDirAddto
+# 1.3.6.1.4.1.12771.4.1.1.109 tnWebVHostDocDirOptions
+# 1.3.6.1.4.1.12771.4.1.1.110 tnWebVHostCgiDirAddto
+# 1.3.6.1.4.1.12771.4.1.1.111 tnWebVHostCgiDirOptions
+### 1.3.6.1.4.1.12771.4.1.1.112 tnWebVHostProtectedDirectory
+### 1.3.6.1.4.1.12771.4.1.1.113 tnWebVHostProtectedName
+### 1.3.6.1.4.1.12771.4.1.1.114 tnWebVHostProtectedType
+# 1.3.6.1.4.1.12771.4.1.1.115 tnWebVHostWebmaster
+# 1.3.6.1.4.1.12771.4.1.1.116 tnWebVHostBindHTTPPort
+# 1.3.6.1.4.1.12771.4.1.1.117 tnWebVHostBindHTTPSPort
+
+# 1.3.6.1.4.1.12771.4.1.2.201 tnDNSdomainname
+# 1.3.6.1.4.1.12771.4.1.2.202 tnDNSsoaPerson
+# 1.3.6.1.4.1.12771.4.1.2.203 tnDNSttl
+# 1.3.6.1.4.1.12771.4.1.2.204 tnDNSnameservers
+# 1.3.6.1.4.1.12771.4.1.2.205 tnDNSprimary
+# 1.3.6.1.4.1.12771.4.1.2.206 tnDNSaRecord
+
+# 1.3.6.1.4.1.12771.4.1.2.391 tnFTPDataDirectory
+
+##############################
+# 1.3.6.1.4.1.12771.4.1.2 ObjectClasses
+# 1.3.6.1.4.1.12771.4.1.2.1 tnClient
+# 1.3.6.1.4.1.12771.4.1.2.2 tnMailDomain
+# 1.3.6.1.4.1.12771.4.1.2.3 tnMailAccount
+# 1.3.6.1.4.1.12771.4.1.2.4 tnMailRemotePerson
+# 1.3.6.1.4.1.12771.4.1.2.5 tnMailAlias
+# 1.3.6.1.4.1.12771.4.1.2.6 tnMailPerson
+# 1.3.6.1.4.1.12771.4.1.2.7 tnUUCPSystem
+
+# 1.3.6.1.4.1.12771.4.1.2.101 tnWebVHost
+### 1.3.6.1.4.1.12771.4.1.2.102 tnWebVHostProtectedArea
+### 1.3.6.1.4.1.12771.4.1.2.103 tnWebVHostProtectedAreaUser
+
+# 1.3.6.1.4.1.12771.4.1.2.201 tnDNSsoa
+# 1.3.6.1.4.1.12771.4.1.2.202 tnDNSsecondary
+# 1.3.6.1.4.1.12771.4.1.2.203 tnDNSrr
+
+# 1.3.6.1.4.1.12771.4.1.2.301 tnFTPuser
+# 1.3.6.1.4.1.12771.4.1.2.302 tnPostgreSQLuser
+# 1.3.6.1.4.1.12771.4.1.2.303 tnPostgreSQLdatabase
+
+
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.1
+ NAME 'tnMailDomainname'
+ DESC 'RFC822 email domain'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.2
+ NAME 'tnHost'
+ DESC 'Server on which this object should be available'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.3
+ NAME 'tnMailMailboxLimit'
+ DESC 'Quota of this Mailbox to be used by Postfix and Courier'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.4
+ NAME 'tnMailRemoteAddress'
+ DESC 'RFC822 email address of a remote person'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.5
+ NAME 'tnMailLocalAddress'
+ DESC 'local RFC822 email address'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.6
+ NAME 'tnMailRouting'
+ DESC 'Reference to the local or remote recipient of this address'
+ SUP distinguishedName )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.7
+ NAME 'tnMailMailboxHomedir'
+ DESC 'Homedirectory of the user to be used by Courier'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.8
+ NAME 'tnMailMailboxLocation'
+ DESC 'Directory where the mailbox is located'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.9
+ NAME 'tnMailMailboxUID'
+ DESC 'Numerical UserID to deliver and fetch as'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.10
+ NAME 'tnMailMailboxGID'
+ DESC 'Numerical GroupID to deliver and fetch as'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.11
+ NAME 'tnMailMailboxAccountname'
+ DESC 'Userid to log on as with Courier'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ )
+#SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.12
+ NAME 'tnMailVirtualAddress'
+ DESC 'virtual RFC822 email address'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.13
+ NAME 'tnMailAliasedAddress'
+ DESC 'aliased RFC822 email address'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.14
+ NAME 'tnMagicDNS'
+ DESC 'Automatically add dns RR for this node'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.15
+ NAME 'tnSSHKey'
+ DESC 'An ssh public key'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.16
+ NAME 'tnUUCPPassword'
+ DESC 'A uucp system password'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.17
+ NAME 'tnUUCPSysName'
+ DESC 'A uucp system name'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.18
+ NAME 'tnMailTransportDestination'
+ DESC 'Destination in a postfix transport map'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.1
+ NAME 'tnClient'
+ DESC 'Client of us'
+ SUP top AUXILIARY
+ MUST ( o )
+ MAY ( userPassword $ description ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.2
+ NAME 'tnMailDomain'
+ DESC 'a domain we handle'
+ SUP top
+ MUST ( tnMailDomainname )
+ MAY ( description $ tnHost $ tnMagicDNS $ tnMailLocalAddress ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.3
+ NAME 'tnMailAccount'
+ DESC 'Account for Mail Person'
+ AUXILIARY
+ MUST ( tnMailMailboxAccountname $ tnMailVirtualAddress $
+ tnMailMailboxHomedir $ tnMailMailboxLocation $
+ tnMailMailboxUID $ tnMailMailboxGID )
+ MAY ( userPassword $ tnMailMailboxLimit ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.4
+ NAME 'tnMailRemotePerson'
+ DESC 'Remote properties of Mail Person'
+ AUXILIARY
+ MUST ( tnMailRemoteAddress ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.5
+ NAME 'tnMailAlias'
+ DESC 'Alias map entry'
+ SUP top
+ MUST ( tnMailAliasedAddress )
+ MAY ( tnHost $ description $ tnMailRouting ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.6
+ NAME 'tnMailPerson'
+ DESC 'Mail Person'
+ SUP top
+ MUST ( cn )
+ MAY ( tnHost $ description $ tnMailLocalAddress ) )
+
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.7
+ NAME 'tnUUCPSystem'
+ DESC 'A UUCP System'
+ SUP top
+ MUST ( tnUUCPSysName $ tnUUCPPassword $ tnSSHKey )
+ MAY ( tnHost $ description $ tnMailDomainname $ tnMagicDNS $
+ tnMailTransportDestination ) )
+
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.8
+ NAME 'tnMailRelay'
+ DESC 'Internet Mail Domains we relay for (i.e. we are backup MX for them)'
+ SUP top
+ MUST ( tnMailDomainname )
+ MAY ( tnMagicDNS $ tnHost $ description ) )
+
+# VHOST ###################################################################
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.101
+ NAME 'tnWebVHostServerName'
+ DESC 'Servername for that vhost'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.102
+ NAME 'tnWebVHostHomeDirectory'
+ DESC 'HomeDirectory of that vhost'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.103
+ NAME 'tnWebVHostProperties'
+ DESC 'Properties for this vhost'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.104
+ NAME 'tnWebVHostDirectoryIndex'
+ DESC 'DirectoryIndex'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.105
+ NAME 'tnWebVHostServerAlias'
+ DESC 'ServerAliases'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.106
+ NAME 'tnWebVHostAddto'
+ DESC 'Optional Parameters'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.107
+ NAME 'tnWebVHostBind'
+ DESC 'Where to bind to'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.108
+ NAME 'tnWebVHostDocDirAddto'
+ DESC 'Optional Parameters for the DocumentRoot Directory'
+ SUP tnWebVHostBind
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.109
+ NAME 'tnWebVHostDocDirOptions'
+ DESC 'Optional Parameters'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.110
+ NAME 'tnWebVHostCgiDirAddto'
+ DESC 'Optional Parameters for the ScriptAlias Directory'
+ SUP tnWebVHostBind
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.111
+ NAME 'tnWebVHostCgiDirOptions'
+ DESC 'Optional Parameters'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+#attributetype ( 1.3.6.1.4.1.12771.4.1.1.112
+# NAME 'tnWebVHostProtectedDirectory'
+# SUP tnWebVHostHomeDirectory
+# SINGLE-VALUE )
+#
+#attributetype ( 1.3.6.1.4.1.12771.4.1.1.113
+# NAME 'tnWebVHostProtectedName'
+# DESC 'AuthName for Protected Area'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+# SINGLE-VALUE )
+#
+#attributetype ( 1.3.6.1.4.1.12771.4.1.1.114
+# NAME 'tnWebVHostProtectedType'
+# DESC 'Type (directory or location) of protected object'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+# SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.115
+ NAME 'tnWebVHostWebmaster'
+ DESC 'email address of webmaster'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.116
+ NAME 'tnWebVHostBindHTTPPort'
+ DESC 'Port to bind this vhost to for SSL'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.1.117
+ NAME 'tnWebVHostBindHTTPSPort'
+ DESC 'Port to bind this vhost to for SSL'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.101
+ NAME 'tnWebVHost'
+ DESC 'Virtual Web Host for Apache'
+ SUP top
+ MUST ( tnWebVHostServerName $ tnWebVHostHomeDirectory )
+ MAY ( tnWebVHostServerAlias $
+ tnWebVHostWebmaster $
+ tnWebVHostProperties $
+ tnWebVHostDirectoryIndex $
+ tnWebVHostAddto $
+ tnWebVHostDocDirOptions $ tnWebVHostDocDirAddto $
+ tnWebVHostCgiDirOptions $ tnWebVHostCgiDirAddto $
+ tnWebVHostBind $
+ tnWebVHostBindHTTPPort $
+ tnWebVHostBindHTTPSPort $
+ tnHost $
+ description $ tnMagicDNS ) )
+
+
+#objectclass ( 1.3.6.1.4.1.12771.4.1.2.102
+# NAME 'tnWebVHostProtectedArea'
+# DESC 'Password Protected Area'
+# SUP top
+# MUST ( tnWebVHostProtectedDirectory $ tnWebVHostProtectedName $
+# tnWebVHostProtectedType )
+# MAY ( description ) )
+#
+#objectclass ( 1.3.6.1.4.1.12771.4.1.2.103
+# NAME 'tnWebVHostProtectedAreaUser'
+# DESC 'User for Password Protected Area'
+# SUP top
+# MUST ( uid )
+# MAY ( userPassword $ description ) )
+
+
+
+
+
+
+# DNS ###################################################################
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.201
+ NAME 'tnDNSdomainname'
+ DESC 'DNS Domain name or domainname part'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.202
+ NAME 'tnDNSsoaPerson'
+ DESC 'DNS SOA Responsible Person'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.203
+ NAME 'tnDNSttl'
+ DESC 'DNS Time To Live'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.204
+ NAME 'tnDNSnameservers'
+ DESC 'Nameservers for this domain (defaults used if empty)'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.205
+ NAME 'tnDNSprimary'
+ DESC 'primary Nameserver (where we fetch the zone from)'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.206
+ NAME 'tnDNSaRecord'
+ DESC 'DNS A record data'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.201
+ NAME 'tnDNSsoa'
+ DESC 'DNS Start Of Authority'
+ SUP top
+ MUST ( tnDNSdomainname )
+ MAY ( tnDNSsoaPerson $ tnDNSttl $ tnDNSnameservers $ description ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.202
+ NAME 'tnDNSsecondary'
+ DESC 'DNS Secondary'
+ SUP top
+ MUST ( tnDNSdomainname $ tnDNSprimary $ tnDNSnameservers )
+ MAY ( description ) )
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.203
+ NAME 'tnDNSrr'
+ DESC 'DNS additional resource record'
+ SUP top
+ MUST ( tnDNSdomainname )
+ MAY ( tnDNSaRecord $ description ) )
+
+
+
+
+# misc ##################################################################
+attributetype ( 1.3.6.1.4.1.12771.4.1.2.391
+ NAME 'tnFTPDataDirectory'
+ DESC 'Directory the user has access to'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+
+
+
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.301
+ NAME 'tnFTPuser'
+ DESC 'FTP user'
+ SUP top
+ MUST ( tnFTPDataDirectory $ uid $ uidNumber $ gidNumber $ homeDirectory )
+ MAY ( tnHost $ description $ tnSSHKey ) )
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.302
+ NAME 'tnPostgreSQLuser'
+ DESC 'PostgreSQL user'
+ SUP top
+ MAY ( tnHost $ description ) )
+objectclass ( 1.3.6.1.4.1.12771.4.1.2.303
+ NAME 'tnPostgreSQLdatabase'
+ DESC 'PostgreSQL database'
+ SUP top AUXILIARY
+ MUST ( cn )
+ MAY ( description ) )
+
+
+
+
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+#######################
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.1
+# NAME 'tnMailVirtualLocalAddress'
+# DESC 'RFC822 email address of this recipient'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.2
+# NAME 'tnMailVirtualRoutingAddress'
+# DESC 'RFC822 routing address of this recipient'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.3
+# NAME 'tnMailMailboxVirtualAddress'
+# DESC 'RFC822 virtual address of this recipient'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.4
+# NAME 'tnMailMailboxLocation'
+# DESC 'Directory where the mailbox is located'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.5
+# NAME 'tnMailMailboxUID'
+# DESC 'Numerical UserID to deliver and fetch as'
+# EQUALITY integerMatch
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.6
+# NAME 'tnMailMailboxGID'
+# DESC 'Numerical GroupID to deliver and fetch as'
+# EQUALITY integerMatch
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.7
+# NAME 'tnMailMailboxLimit'
+# DESC 'Quota of this Mailbox to be used by Postfix and Courier'
+# EQUALITY integerMatch
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.8
+# NAME 'tnMailMailboxAccountname'
+# DESC 'Userid to log on as with Courier'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.9
+# NAME 'tnMailMailboxHomedir'
+# DESC 'Homedirectory of the user to be used by Courier'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.10
+# NAME 'tnMailMailboxAccountowner'
+# SUP distinguishedName )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.11
+# NAME 'tnMailTransportDomain'
+# DESC 'RFC822 email domain'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.12
+# NAME 'tnMailTransportDestination'
+# DESC 'Transport Destination'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.13
+# NAME 'tnMailRelayDomain'
+# DESC 'RFC822 domain name to relay for'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.14
+# NAME 'tnHost'
+# DESC 'Server on which this object should be available'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.15
+# NAME 'tnMailCanonicalLocalAddress'
+# DESC 'pattern (left side) in the canonical table'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.16
+# NAME 'tnMailCanonicalRoutingAddress'
+# DESC 'result (right side) in the canonical table'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.1
+# NAME 'tnMailVirtualEntry'
+# DESC 'Internet Mail recipient to be used in Postfix virtual maps'
+# SUP top AUXILIARY
+# MUST ( tnMailVirtualLocalAddress $ tnMailVirtualRoutingAddress )
+# MAY ( tnHost ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.2
+# NAME 'tnMailMailbox'
+# DESC 'Internet Mail mailbox to be used in Postfix virtual mailbox maps'
+# SUP top AUXILIARY
+# MUST ( tnMailMailboxVirtualAddress $ tnMailMailboxLocation $
+# tnMailMailboxUID $ tnMailMailboxGID )
+# MAY ( tnMailMailboxLimit $ tnHost ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.3
+# NAME 'tnMailAccount'
+# DESC 'Internet Mail IMAP/POP3 account to be used by Courier'
+# SUP top AUXILIARY
+# MUST ( tnMailMailboxAccountname $ tnMailMailboxHomedir $
+# tnMailMailboxLocation $ tnMailMailboxUID $ tnMailMailboxGID )
+# MAY ( tnMailMailboxLimit $ userPassword $ tnMailMailboxAccountowner $
+# tnHost ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.4
+# NAME 'tnMailTransport'
+# DESC 'Internet Mail transport to be used in Postfix transport maps'
+# SUP top AUXILIARY
+# MUST ( tnMailTransportDomain $ tnMailTransportDestination )
+# MAY ( tnHost ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.5
+# NAME 'tnMailRelay'
+# DESC 'Internet Mail Domains to be used in Postfix relay_domains'
+# SUP top AUXILIARY
+# MUST ( tnMailRelayDomain )
+# MAY ( tnHost $ description ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.6
+# NAME 'tnClient'
+# DESC 'Client of us'
+# SUP top AUXILIARY )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.7
+# NAME 'tnMailCanonical'
+# DESC 'Postfix Canonical Table'
+# SUP top AUXILIARY
+# MUST ( tnMailCanonicalLocalAddress $ tnMailCanonicalRoutingAddress )
+# MAY ( tnHost $ description ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.8
+# NAME 'tnMailSenderCanonical'
+# DESC 'Client of us'
+# SUP tnMailCanonical AUXILIARY )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.9
+# NAME 'tnMailRecipientCanonical'
+# DESC 'Client of us'
+# SUP tnMailCanonical AUXILIARY )
+#
+#
+#
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.102
+# NAME 'tnWebVHostServerName'
+# DESC 'Servername for that vhost'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.103
+# NAME 'tnWebVHostHomeDirectory'
+# DESC 'HomeDirectory of that vhost'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# # 104 was tnWebVHostServer
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.105
+# NAME 'tnWebVHostProperties'
+# DESC 'Properties for this vhost'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.106
+# NAME 'tnWebVHostDirectoryIndex'
+# DESC 'DirectoryIndex'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.107
+# NAME 'tnWebVHostRedirect'
+# DESC 'Redirects - from and to are whitespace seperated'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.108
+# NAME 'tnWebVHostServerAlias'
+# DESC 'ServerAliases'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.109
+# NAME 'tnWebVHostOptionals'
+# DESC 'Optional Parameters'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.110
+# NAME 'tnWebVHostBind'
+# DESC 'Where to bind to'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.111
+# NAME 'tnWebVHostOptionalsDocDir'
+# DESC 'Optional Parameters for the DocumentRoot Directory'
+# SUP tnWebVHostBind )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.112
+# NAME 'tnWebVHostOptionalsCgiDir'
+# DESC 'Optional Parameters for the ScriptAlias Directory'
+# SUP tnWebVHostBind )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.113
+# NAME 'tnWebVHostWebDAVdomain'
+# DESC 'Servername for webdav vhost'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.114
+# NAME 'tnWebVHostOptionsDocDir'
+# DESC 'Optional Parameters'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.115
+# NAME 'tnWebVHostOptionsCgiDir'
+# DESC 'Optional Parameters'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.116
+# NAME 'tnWebVHostProtectedDirectory'
+# SUP tnWebVHostHomeDirectory
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.117
+# NAME 'tnWebVHostProtectedName'
+# DESC 'AuthName for Protected Area'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.118
+# NAME 'tnWebVHostProtectedType'
+# DESC 'Type (directory or location) of protected object'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+# SINGLE-VALUE )
+#
+#
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.102
+# NAME 'tnWebVHost'
+# DESC 'Virtual Web Host for Apache'
+# SUP top AUXILIARY
+# MUST ( tnWebVHostServerName $ tnWebVHostHomeDirectory )
+# MAY ( tnWebVHostProperties $ tnWebVHostDirectoryIndex $
+# tnWebVHostRedirect $ tnWebVHostServerAlias $
+# tnWebVHostOptionals $ tnWebVHostOptionalsDocDir $
+# tnWebVHostOptionalsCgiDir $ tnWebVHostOptionsDocDir $
+# tnWebVHostOptionsCgiDir $ tnWebVHostBind $
+# tnWebVHostWebDAVdomain $ tnHost $ description ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.103
+# NAME 'tnWebVHostProtectedArea'
+# DESC 'Password Protected Area'
+# SUP top AUXILIARY
+# MUST ( tnWebVHostProtectedDirectory $ tnWebVHostProtectedName $
+# tnWebVHostProtectedType )
+# MAY ( description ) )
+#
+#
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.201
+# NAME 'tnDNSdomainname'
+# DESC 'DNS Domain name or domainname part'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.202
+# NAME 'tnDNSsoaOrigin'
+# DESC 'DNS SOA Origin'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.203
+# NAME 'tnDNSsoaPerson'
+# DESC 'DNS SOA Responsible Person'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.204
+# NAME 'tnDNSsoaSerial'
+# DESC 'DNS SOA Serial'
+# EQUALITY integerMatch
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.205
+# NAME 'tnDNSsoaRefresh'
+# DESC 'DNS SOA Refresh'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.206
+# NAME 'tnDNSsoaRetry'
+# DESC 'DNS SOA Retry'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.207
+# NAME 'tnDNSsoaExpire'
+# DESC 'DNS SOA Expire'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.208
+# NAME 'tnDNSsoaMinimum'
+# DESC 'DNS SOA Minimum'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.209
+# NAME 'tnDNSttl'
+# DESC 'DNS Time To Live'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.210
+# NAME 'tnDNSclass'
+# DESC 'DNS Class (always IN)'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.211
+# NAME 'tnDNSrrType'
+# DESC 'DNS Type'
+# EQUALITY caseIgnoreIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+# SINGLE-VALUE )
+#
+# attributetype ( 1.3.6.1.4.1.12771.1.2.1.212
+# NAME 'tnDNSrrData'
+# DESC 'DNS Data'
+# EQUALITY caseExactIA5Match
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+#
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.201
+# NAME 'tnDNSsoa'
+# DESC 'DNS Start Of Authority'
+# SUP top AUXILIARY
+# MUST ( tnDNSdomainname $ tnDNSsoaOrigin $ tnDNSsoaPerson )
+# MAY ( tnDNSsoaSerial $ tnDNSsoaRefresh $ tnDNSsoaRetry $
+# tnDNSsoaExpire $ tnDNSsoaMinimum $
+# tnDNSttl $ tnDNSclass $ description ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.202
+# NAME 'tnDNSrr'
+# DESC 'DNS Ressource Record'
+# SUP top AUXILIARY
+# MUST ( tnDNSdomainname $ tnDNSrrType $ tnDNSrrData )
+# MAY ( tnDNSttl $ tnDNSclass $ description ) )
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.203
+# NAME 'tnDNSsecondary'
+# DESC 'DNS Secondary'
+# SUP top AUXILIARY
+# MUST ( tnDNSdomainname $ tnDNSsoaOrigin )
+# MAY ( tnHost $ description ) )
+#
+#
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.301
+# NAME 'tnFTPuser'
+# DESC 'FTP user'
+# SUP top AUXILIARY
+# MAY ( tnHost $ description ) )
+# objectclass ( 1.3.6.1.4.1.12771.1.2.2.302
+# NAME 'tnPostgreSQLuser'
+# DESC 'PostgreSQL user'
+# SUP top AUXILIARY
+# MAY ( tnHost $ description ) )
+#
diff --git a/site-ruby/myldap.rb b/site-ruby/myldap.rb
new file mode 100644
index 0000000..115d772
--- /dev/null
+++ b/site-ruby/myldap.rb
@@ -0,0 +1,88 @@
+#
+# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org>
+#
+# All rights reserved.
+#
+
+class MyLDAP
+ def initialize(config, use = nil)
+ @conn = LDAP::Conn.new(config['ldapserver'], config['ldapport'])
+ @basedn = config['basedn']
+ if use
+ @binddn = config['credentials'][use]['binddn']
+ @bindpw = config['credentials'][use]['bindpw']
+ else
+ myconfig = YAML::load( File.open( File.expand_path('~/.noreply.ldap') ) )
+ @binddn = myconfig['binddn']
+ @bindpw = myconfig['bindpw']
+ end
+ unless @conn.bind(@binddn, @bindpw)
+ @conn.perror("bind")
+ end
+ end
+
+ def add(dn, data)
+ begin
+ entry = data.map{
+ |key, value|
+ LDAP.mod(LDAP::LDAP_MOD_ADD, key, value)
+ }
+ @conn.add(dn, entry)
+ rescue LDAP::ResultError
+ @conn.perror("add")
+ return false
+ end
+ @conn.perror("add")
+ return true
+ end
+
+ def conn()
+ return @conn
+ end
+
+ def verify_client(client)
+ begin
+ clients = @conn.search2(@basedn, LDAP::LDAP_SCOPE_SUBTREE,
+ '(&(objectclass=tnClient)(o='+client+'))')
+ rescue LDAP::ResultError => msg
+ $stderr.print(msg)
+ exit 1
+ end
+
+ if clients.length != 1
+ STDERR.puts "Found %s clients with o=%s"%[clients.length, client]
+ exit 1
+ end
+ return clients.pop
+ end
+
+ def verify_local_domains_exist(addresses)
+ domains = addresses.collect{ |a|
+ a =~ /@(.*)/
+ domain = $1
+ unless domain
+ STDERR.puts "%s is no email address"%[a]
+ exit 1
+ end
+ domain
+ }.uniq
+
+ domains.each { |d|
+ begin
+ doms = @conn.search2(@basedn, LDAP::LDAP_SCOPE_SUBTREE,
+ '(&(objectclass=tnMailDomain)(tnMailDomainname='+d+'))')
+ rescue LDAP::ResultError => msg
+ $stderr.print(msg)
+ exit 1
+ end
+
+ if doms.length != 1
+ STDERR.puts "Found %s tnMailDomains with tnMailDomainname=%s"%[doms.length, d]
+ exit 1
+ end
+
+ puts "Domain %s: check"%[d]
+ }
+ end
+end
+