#!/kolab/bin/perl

# (c) 2003 Tassilo Erlewein <tassilo.erlewein@erfrakon.de>
# (c) 2003 Martin Konold <martin.konold@erfrakon.de>
# (c) 2003 Achim Frank <achim.frank@erfrakon.de>
# This program is Free Software under the GNU General Public License (>=v2).
# Read the file COPYING that comes with this packages for details.

# kolab_bootstrap Version 0.91
# create unix configuration files from data source (files or LDAP)
# and templates

use strict;
use vars qw($opt_b);

use URI;
use IO::File;
use IO::Select;
use Net::LDAP;
use Net::LDAP::Entry;
use Net::Netmask;
use File::Copy;
use Getopt::Std;
use Sys::Hostname;
use Term::ReadKey;

my $kolab_prefix = "/kolab";
my $kolab_config = $kolab_prefix."/etc/kolab/kolab.conf";

print "\nKOLAB BOOTSTRAP\n\n";

my $fd = IO::File->new($kolab_config, "r")
   || die "could not open $kolab_config";
my %kolab_config;
foreach (<$fd>) {
   if (/(.*) : (.*)/) {
      $kolab_config{$1} = $2;
   }
}
undef $fd;
my $bind_dn = $kolab_config{'bind_dn'} || die "could not read bind_dn from $kolab_config";
my $bind_pw = $kolab_config{'bind_pw'} || die "could not read bind_pw from $kolab_config";
my $ldap_uri = $kolab_config{'ldap_uri'} || die "could not read ldap_uri from $kolab_config";
my $base_dn = $kolab_config{'base_dn'} || die "could not read base_dn from $kolab_config";
my $php_dn = $kolab_config{'php_dn'} || die "could not read php_dn from $kolab_config";
my $php_pw = $kolab_config{'php_pw'} || die "could not read php_pw from $kolab_config";

if (!$bind_dn || !$bind_pw || !$ldap_uri || !$base_dn) {
   print "Please check $kolab_config/kolab.conf (seems to be incomplete)\n";
   die "and run kolab_bootstrap afterwards, manually";
}
my $fqdn = `hostname -f`;
chomp($fqdn);

print "Please enter Hostname [$fqdn]:";
my $tmp = ReadLine;
chomp $tmp;
if ($tmp) { $fqdn = $tmp; }
print "proceeding with Hostname $fqdn\n";

(my $dummy, my $domain) = split(/\./, $fqdn, 2);
if (!$domain) { $domain = $fqdn; }

print "Please enter your Maildomain [$domain]:";
my $tmp = ReadLine;
chomp $tmp;
if ($tmp) { $domain = $tmp; }
print "proceeding with Maildomain $domain\n";

if ($base_dn =~ /\@\@\@/ || $bind_dn =~ /\@\@\@/ || $bind_pw =~ /\@\@\@/) {
   print "Generating default configuration:\n";
   if ($base_dn =~ /\@\@\@/) {
      $base_dn = "";
      foreach my $dc ((split(/\./,$fqdn))) { $base_dn .= "dc=$dc,"; }
      chop $base_dn;
      print " base_dn : $base_dn\n";
   }
   if ($bind_dn =~ /\@\@\@/) {
      $bind_dn =~ s/\@\@\@kolab_basedn\@\@\@/$base_dn/g;
      print " bind_dn : $bind_dn\n";
   }
   if ($bind_pw =~ /\@\@\@/) {
      $bind_pw = `/kolab/bin/openssl passwd kolab`;
      chomp $bind_pw;
      print "Please choose a manager password [$bind_pw]:";
      my $tmp = ReadLine;
      chomp $tmp;
      if ($tmp) { $bind_pw = $tmp; }
      print " bind_pw : $bind_pw\n";
   }
   if ($php_dn =~ /\@\@\@/) {
      $php_dn =~ s/\@\@\@kolab_basedn\@\@\@/$base_dn/g;
   }
   if ($php_pw =~ /\@\@\@/) {
      $php_pw = `/kolab/bin/openssl passwd nobody`;
      chomp $php_pw;
   }

   $fd = IO::File->new($kolab_config, "w+") || die "could not open $kolab_config";
   print $fd "base_dn : $base_dn\n";
   print $fd "bind_dn : $bind_dn\n";
   print $fd "bind_pw : $bind_pw\n";
   print $fd "ldap_uri : $ldap_uri\n";
   print $fd "php_dn : $php_dn\n";
   print $fd "php_pw : $php_pw\n";
   undef $fd;
   print "done modifying $kolab_config\n\n";
   print "IMPORTANT NOTE:\n";
   print "use login=manager and passwd=$bind_pw when you log into the webinterface!\n\n";
}

# remove all application specific fsl config files as these are yet to be done
# having invalid files there hinders applications from starting up properly
# we delay this until there's a better understanding of the fsl stuff

#unlink("$kolab_prefix/etc/fsl/fsl.postfix");
#unlink("$kolab_prefix/kolab/etc/fsl/fsl.sasl");
#unlink("$kolab_prefix/etc/fsl/fsl.apache");
#unlink("$kolab_prefix/etc/fsl/fsl.slapd");
#unlink("$kolab_prefix/kolab/etc/fsl/fsl.imapd");

my $confname = "$kolab_prefix/etc/sasl/apps/smtpd.conf";
copy("$kolab_prefix/etc/kolab/smtpd.conf.template", $confname) || die "could not write to $confname";

getopts('b');

if ($opt_b) {
   print "prepare LDAP database...\n";
   if ($ldap_uri =~ /127\.0\.0\.1/ || $ldap_uri =~ /localhost/) {
      print "kill running slapd (if any)\n";
      system("killall -INT slapd >/dev/null 2>&1");
      sleep 1;
      system("killall -INT slapd >/dev/null 2>&1");
      sleep 1;
      system("killall -9 slapd >/dev/null 2>&1");
      sleep 1;
      system("killall -9 slapd >/dev/null 2>&1");
      sleep 1;
      my $tmpl = IO::File->new("$kolab_prefix/etc/kolab/slapd.conf.template", "r")
        || die "could not read $kolab_prefix/etc/kolab/slapd.conf.template";
      my $slpd = IO::File->new("$kolab_prefix/etc/openldap/slapd.conf","w+")
        || die "could not write to $kolab_prefix/etc/openldap/slapd.conf";
      foreach (<$tmpl>) {
         s/\@\@\@base_dn\@\@\@/$base_dn/g;
         s/\@\@\@bind_dn\@\@\@/$bind_dn/g;
         s/\@\@\@bind_pw\@\@\@/$bind_pw/g;
	 s/TLSCertificate/\#TLSCertificate/g;
         print $slpd $_;
      }
      undef $slpd;
      undef $tmpl;
      # now we must startup slapd
      print "temporarily start slapd\n";
      $ldap_uri = "ldap://127.0.0.1:389/";
      system("$kolab_prefix/libexec/slapd -h ldap://127.0.0.1:389/ -f $kolab_prefix/etc/openldap/slapd.conf");
      sleep 3;
   }

   my $ldapuri = URI->new($ldap_uri) || warn "error: could not parse given uri";
   my $ldap = Net::LDAP->new($ldapuri->host, port=> $ldapuri->port) || warn "could not connect ldap server";
   if ($ldap) {
      $ldap->bind($bind_dn, password=> $bind_pw) || warn "could not bind to ldap";
      my $mesg = $ldap->search(base=> "$base_dn", scope=> 'exact', filter=> "(objectclass=*)");
      if ($mesg && $mesg->count != 1) {
         print "no $base_dn object found, creating one\n";
	 my $hostname = (split(/\./,$fqdn))[0];
         chomp $hostname;
         $mesg = $ldap->add( $base_dn, attr=> [dc=> $hostname, 'objectclass'=> ['top', 'domain'] ]);
      } 
      $mesg && $mesg->code && warn "failed to write basedn entry : ", $mesg->error;
      $mesg = $ldap->search(base=> "k=kolab,$base_dn", scope=> 'exact', filter=> "(objectclass=*)");
      if ($mesg && $mesg->count != 1) {
         print "no kolab config object in ldap, generating a reasonable default\n";
      } else {
         print "modifying existing kolab config object\n";
      }

      # create kolab config object
      my $ldapobject = Net::LDAP::Entry->new;
      my $mynetworkinterfaces = "127.0.0.0/8";
      my @net=`/sbin/ifconfig -a | grep -v 127.0.0 | grep -i \"inet\"`;
      chomp @net;
      foreach (@net) {
         /127\.0\.0/ && next;
         s/^ *(.*)/$1/g;
         my @tmp = split / /;
         my $ip;
         my $mask;
         foreach (@tmp) {
            if (/addr.*:(.*)$/i) { $ip = $1; }
            if (/mask.*:(.*)$/i) { $mask = $1 }
         }
         if ($ip && $mask) {
            my $tmp = new Net::Netmask ($ip."/".$mask);
            $mynetworkinterfaces .= ", ".$tmp->base()."/".$tmp->bits();
         }
      }
      print "mynetworkinterfaces: ".$mynetworkinterfaces."\n";

      $ldapobject->replace(
	'k' => 'kolab',
        'fqhostname' => $fqdn,
        'postfix-mydomain' => $domain,
        #'postfix-relaydomains' => "",
        'postfix-mydestination' => "\$mydomain",
        'postfix-mynetworks' => $mynetworkinterfaces,
        #'postfix-relayhost' => "",
        #'postfix-transport' => "",
        'cyrus-autocreatequota' => 100000,
        'cyrus-admins' => "manager",
        'cyrus-imap' => "TRUE",
        'cyrus-pop3' => "FALSE",
        'cyrus-imaps' => "TRUE",
        'cyrus-pop3s' => "TRUE",
        'cyrus-sieve' => "TRUE",
        'apache-http' => "FALSE",
        'proftpd-ftp' => "FALSE",
        #'proftpd-defaultquota' => 100000,
        #'proftpd-userPassword' => "freebusy",
	'uid' => "freebusy",
        'userPassword' => "freebusy",
        'objectclass' => ['top', 'kolab' ] );
      $ldapobject->dn("k=kolab,$base_dn");
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      # create internal user topnode
      $ldapobject = Net::LDAP::Entry->new;
      $ldapobject->replace('cn' => 'internal', 'objectclass' => ['top','namedObject']);
      $ldapobject->dn("cn=internal,$base_dn");
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      # create external user topnode
      $ldapobject = Net::LDAP::Entry->new;
      $ldapobject->replace('cn' => 'external', 'objectclass' => ['top','namedObject']);
      $ldapobject->dn("cn=external,$base_dn");
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      # create admin group
      $ldapobject = Net::LDAP::Entry->new;
      $ldapobject->replace('cn' => 'admin', 'objectclass' => ['top','groupOfNames'],
                           'member' => "cn=manager,$base_dn");
      $ldapobject->dn("cn=admin,$base_dn");
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      # create manager user
      $ldapobject = Net::LDAP::Entry->new;
      $ldapobject->replace('cn' => 'manager', 'sn' => 'n/a', 'uid' => 'manager',
			   'userPassword' => $bind_pw, 'objectclass' => ['top','inetOrgPerson']);
      $ldapobject->dn($bind_dn);
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      # create php read-only user
      $ldapobject = Net::LDAP::Entry->new;
      $ldapobject->replace('cn' => 'nobody', 'sn' => 'n/a n/a', 'uid' => 'nobody',
      			   'userPassword' => $php_pw, 'objectclass' => ['top','inetOrgPerson']);
      $ldapobject->dn("cn=nobody,$base_dn");
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      # create mainainter group
      $ldapobject = Net::LDAP::Entry->new;
      $ldapobject->replace('cn' => 'maintainer', 'objectclass' => ['top','groupOfNames']);
      $ldapobject->dn("cn=maintainer,$base_dn");
      $mesg = $ldapobject->update($ldap);
      $mesg && $mesg->code && warn "failed to write entry: ", $mesg->error;
      undef $ldapobject;

      $ldap->unbind;
   }
   print "LDAP setup finished\n\n";
 
   print "Create initial config files for postfix, apache, proftpd, cyrus imap, saslauthd\n"; 
   print " running $kolab_prefix/etc/kolab/kolab -v -o -l$ldap_uri\n"; 
   system("$kolab_prefix/etc/kolab/kolab -v -o -l$ldap_uri");

   if ($ldap_uri =~ /127\.0\.0\.1/ || $ldap_uri =~ /localhost/) {
      print "\nkill temporary slapd\n\n";
      system("killall -INT slapd >/dev/null 2>&1");
      system("killall -INT slapd >/dev/null 2>&1");
      system("killall -9 slapd >/dev/null 2>&1");
      system("killall -9 slapd >/dev/null 2>&1");
   }

   system("/kolab/etc/kolab/kolab_sslcert.sh $fqdn");
   print "kolab should now be ready to run\n";
   print "please run '/kolab/etc/rc.d/rc.kolab start'\n";

   exit;
}

