From c88bc35f1c88d9fbbba6706a4abaad24a1868c98 Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Wed, 18 Oct 2006 11:33:32 +0000 Subject: Add hosting ldap git-svn-id: svn+ssh://asteria.noreply.org/svn/weaselutils/trunk@190 bc3d92e2-beff-0310-a7cd-cc87d7ac0ede --- bin/ldap.add.client | 114 +++++ bin/ldap.add.dns | 49 +++ bin/ldap.add.dns.secondary | 57 +++ bin/ldap.add.ftp | 76 ++++ bin/ldap.add.mail.domain | 66 +++ bin/ldap.add.mail.localperson | 70 +++ bin/ldap.add.mail.remoteperson | 65 +++ bin/ldap.add.mail.uucp | 67 +++ bin/ldap.add.pg | 60 +++ bin/ldap.add.staff | 51 +++ bin/ldap.add.vhost | 63 +++ bin/ldap2analog | 178 ++++++++ bin/ldap2apache | 236 +++++++++++ bin/ldap2bind | 344 +++++++++++++++ bin/ldap2chroot-accounts | 139 ++++++ bin/ldap2mail | 29 ++ bin/ldap2passwd | 94 ++++ bin/ldap2postgres | 138 ++++++ bin/ldap2uucp | 99 +++++ schema/3node2.schema | 943 +++++++++++++++++++++++++++++++++++++++++ site-ruby/myldap.rb | 88 ++++ 21 files changed, 3026 insertions(+) create mode 100755 bin/ldap.add.client create mode 100755 bin/ldap.add.dns create mode 100755 bin/ldap.add.dns.secondary create mode 100755 bin/ldap.add.ftp create mode 100755 bin/ldap.add.mail.domain create mode 100755 bin/ldap.add.mail.localperson create mode 100755 bin/ldap.add.mail.remoteperson create mode 100755 bin/ldap.add.mail.uucp create mode 100755 bin/ldap.add.pg create mode 100755 bin/ldap.add.staff create mode 100755 bin/ldap.add.vhost create mode 100755 bin/ldap2analog create mode 100755 bin/ldap2apache create mode 100755 bin/ldap2bind create mode 100755 bin/ldap2chroot-accounts create mode 100755 bin/ldap2mail create mode 100755 bin/ldap2passwd create mode 100755 bin/ldap2postgres create mode 100755 bin/ldap2uucp create mode 100644 schema/3node2.schema create mode 100644 site-ruby/myldap.rb 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 +# +# 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 [--password ] [--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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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.)") { |@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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 Epp@3node.com + +=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 +# +# 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 << "" + config << " SSLEngine on" + config << " SSLCertificateFile #{crtfile}" + config << " SSLCertificateKeyFile #{keyfile}" + config << ' ' + config << ' SSLOptions +StdEnvVars' + config << ' ' + config << ' SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown' + config << '' + else + config << "" + 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 << "# " + config << " " + 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 << " " + config << "# " + if property['cgi-bin'] == "yes" + cgihome = home.gsub(/^\/srv\/www\/vhosts/, '/var/www/vhosts') + config << " ScriptAlias /cgi-bin #{cgihome}/cgi-bin" + config << " " + config << " AllowOverride FileInfo AuthConfig Limit Indexes" + config << " Options ExecCGI #{cgidiroptions}" + config << " #{cgidiraddto}" if cgidiraddto + config << " " + end + config << " #{addto}" if addto + config << "" + 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 << "" + 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 << "" + 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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(< + +# 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(< +system + time any + called-login + protocol it + + +# 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(< +# +no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/sbin/uucico -D -l" + +# 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 +# +# 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 +# +# 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 + -- cgit v1.2.3