#!/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 Version 1.0

#use strict;
use URI;
use IO::Select;
use IO::Socket;
use IO::File;
use Convert::ASN1 qw(:io);
use Net::LDAP::ASN qw(LDAPRequest LDAPResponse LDAPResult);
use Net::LDAP::Constant;
use Net::LDAP;
use File::Copy;
use Getopt::Std;
use Cyrus::IMAP::Admin;
use Sys::Syslog;
use Data::Dumper;
use vars qw($opt_v $opt_o $opt_l);

getopts('vol:');
my $kolab_prefix="/kolab";
my %kolab_config;
my %configdata=();
my %haschanged=();
my $postmap="$kolab_prefix/sbin/postmap";
my $newaliases="$kolab_prefix/sbin/newaliases";
my $conn;
my $server;
my $reinit=0;

sub PROTOCOLOP_BINDREQUEST   	() { 0x00 }
sub PROTOCOLOP_BINDRESPONSE  	() { 0x01 }
sub PROTOCOLOP_UNBINDREQUEST 	() { 0x02 }
sub PROTOCOLOP_SEARCHREQUEST 	() { 0x03 }
sub PROTOCOLOP_SEARCHRESENTRY  	() { 0x04 }
sub PROTOCOLOP_SEARCHRESDONE  	() { 0x05 }
sub PROTOCOLOP_SEARCHRESREF  	() { 0x06 }
sub PROTOCOLOP_MODIFYREQUEST  	() { 0x07 }
sub PROTOCOLOP_MODIFYRESPONSE  	() { 0x08 }
sub PROTOCOLOP_ADDREQUEST  	() { 0x09 }
sub PROTOCOLOP_ADDRESPONSE  	() { 0x10 }
sub PROTOCOLOP_DELREQUEST  	() { 0x11 }
sub PROTOCOLOP_DELRESPONSE  	() { 0x12 }
sub PROTOCOLOP_MODDNREQUEST  	() { 0x13 }
sub PROTOCOLOP_MODDNRESPONSE  	() { 0x14 }
sub PROTOCOLOP_COMPAREREQUEST  	() { 0x15 }
sub PROTOCOLOP_COMPARERESPONSE  () { 0x16 }
sub PROTOCOLOP_ABANDONREQUEST  	() { 0x17 }
sub PROTOCOLOP_EXTENDEDREQ  	() { 0x18 }
sub PROTOCOLOP_EXTENDEDRESP  	() { 0x19 }


#############
# FUNCTIONS #
#############

sub dolog 
{
  syslog('info', "$_[0]");
}

sub sigfunction
{
   if ($conn) { undef $conn; }
   if ($server) { undef $server; }
   dolog("exiting");
   exit(0);
}
$SIG{'INT'} = 'sigfunction';
$SIG{'TERM'} = 'sigfunction';

sub reinit
{
   dolog("HUP signal received, closeing socket and restart listening");
   configchange();   
   $reinit=1;
}
$SIG{'HUP'} = 'reinit';

sub debug
{
   #my $a = shift;
   #print "$a\n";
   #dolog($a);
}

sub debug_response
{ 
   #my $p = shift;
   #$Data::Dumper::Indent=1;
   #$Data::Dumper::Quotekeys=0;
   #print Dumper($LDAPResponse->decode($p));
}

sub debug_request
{
   #my $p = shift;
   #$Data::Dumper::Indent=1;
   #$Data::Dumper::Quotekeys=0;
   #print Dumper($LDAPRequest->decode($p));
}

sub get_request_type
{
   my $op = shift;
   if ($op->{bindRequest}) { return "bindRequest"; }
   if ($op->{unbindRequest}) { return "unbindRequest"; }
   if ($op->{addRequest}) { return "addRequest"; }
   if ($op->{delRequest}) { return "delRequest"; }
   if ($op->{modifyRequest}) { return "modifyRequest"; }
   if ($op->{modDNRequest}) { return "modDNRequest"; }
   if ($op->{searchRequest}) { return "searchRequest"; }
   if ($op->{compareRequest}) { return "compareRequest"; }
   if ($op->{abandonRequest}) { return "abandonRequest"; }
   if ($op->{extendedRequest}) { return "extendedRequest"; }
   return "";
}

sub bind_response
{
   my $req = shift;
   debug("got bind request");
   my $pdu = $LDAPResponse->encode(
                messageID => $req->{messageID},
                protocolOp => {
                   choiceID => PROTOCOLOP_BINDRESPONSE,
                   bindResponse => {
                      resultCode => Net::LDAP::Constant::LDAP_SUCCESS,
                      matchedDN => $req->{bindRequest}{name},
                      errorMessage => "",
                      serverSaslCreds => "" }}) || die $LDAPResponse->error;
   return $pdu;
}

sub add_response
{
   my $req = shift;
   debug("got add request");
   my $pdu = $LDAPResponse->encode(
                messageID => $req->{messageID},
                protocolOp => {
                   choiceID => PROTOCOLOP_ADDRESPONSE,
                   addResponse => {
                      resultCode => Net::LDAP::Constant::LDAP_SUCCESS,
                      matchedDN => $req->{addRequest}{objectName},
                      errorMessage => "" }}) || die $LDAPResponse->error;
   return $pdu;
}

sub del_response
{
   my $req = shift;
   debug("got del request");
   my $pdu = $LDAPResponse->encode(
                messageID => $req->{messageID},
                protocolOp => {
                   choiceID => PROTOCOLOP_DELRESPONSE,
                   delResponse => {
                      resultCode => Net::LDAP::Constant::LDAP_SUCCESS,
                      matchedDN => $req->{delRequest},
                      errorMessage => "" }}) || die $LDAPResponse->error;
   return $pdu;
}

sub mod_response
{   
   my $req = shift;
   debug("got mod request");
   my $pdu = $LDAPResponse->encode(
                messageID => $req->{messageID},
                protocolOp => {
                   choiceID => PROTOCOLOP_MODIFYRESPONSE,
                   modifyResponse => {
                      resultCode => Net::LDAP::Constant::LDAP_SUCCESS,
                      matchedDN => $req->{modifyRequest}{object},
                      errorMessage => "" }}) || die $LDAPResponse->error;
   return $pdu;
}

sub moddn_response
{
   my $req = shift;
   debug("got moddn request");
   my $pdu = $LDAPResponse->encode(
                messageID => $req->{messageID},
                protocolOp => {
                   choiceID => PROTOCOLOP_MODDNRESPONSE,
                   modDNResponse => {
                      resultCode => Net::LDAP::Constant::LDAP_SUCCESS,
                      matchedDN => $req->{modDNRequest}{entry},
                      errorMessage => "" }}) || die $LDAPResponse->error;
   return $pdu;
}

# we will often trim strings and kill leading and trailing whitespace
sub trim {
  my $string = $_[0];
  $string =~ s/^\s+//g;
  $string =~ s/\s+$//g;
  chomp $string;
  return $string;
}

# build ( <template name> , <new config file name> )
sub build {
   my $templ = $_[0];
   my $conf = $_[1];
   my $uid = (getpwnam("kolab"))[2];
   my $gid = (getgrnam("kolab"))[2];
   $opt_v && print "creating new $conf from $templ\n";

   # make a copy of the last config file to detect differences
   # we want to avoid restarting services unless necessary
   if (!$opt_o) { 
      copy($conf, $conf.".old");
      chown($uid,$gid,$conf.".old");
   }

   my $template = IO::File->new($templ, "r") || die "could not open $templ";
   my $config = IO::File->new($kolab_prefix."/etc/kolab/.tmp", "w+") || die "could not open $conf";
   while (<$template>) {
      if (/\@{3}(\S+)\@{3}/) {
         if ($configdata{$1}) {
            s/\@{3}(\S+)\@{3}/$configdata{$1}/g;
         } else {
            dolog("no replacement for substitute $1");
            s/\@{3}(\S+)\@{3}//g;
         }
      }
      print $config $_;
   }
   undef $template;
   undef $config;
   move($kolab_prefix."/etc/kolab/.tmp", $conf);
   chown($uid,$gid,$conf);

   # find out about changes   
   if (!$opt_o && -f $conf.".old") {
         my $rc = `diff -q $conf $conf.old`;
         if ($rc) {
         if ($conf =~ /postfix/) {
            $haschanged{'postfix'} = 1;
         } elsif ($conf =~ /saslauthd/) {
            $haschanged{'saslauthd'} = 1;
         } elsif ($conf =~ /apache/) {
            $haschanged{'apache'} = 1;
         } elsif ($conf =~ /proftpd/) {
            $haschanged{'proftpd'} = 1;
         } elsif ($conf =~ /openldap/) {
            $haschanged{'slapd'} = 1;
         } elsif ($conf =~ /imapd/) {
            $haschanged{'imapd'} =1;
         }
 	 chomp($rc);
         dolog($rc);
      }
   }
}

sub configchange
{
   my $ldap;
   my $ldapuri;
   my $key;
   my $value;
   my $section="";
   my $ldapobject;
   my $mesg;
   my %config_files = (
      "$kolab_prefix/etc/kolab/session_vars.php.template" => "$kolab_prefix/var/kolab/www/admin/include/session_vars.php",

      "$kolab_prefix/etc/kolab/main.cf.template" => "$kolab_prefix/etc/postfix/main.cf",
      "$kolab_prefix/etc/kolab/master.cf.template" => "$kolab_prefix/etc/postfix/master.cf",

      "$kolab_prefix/etc/kolab/saslauthd.conf.template" => "$kolab_prefix/etc/sasl/saslauthd.conf",

      "$kolab_prefix/etc/kolab/imapd.conf.template" => "$kolab_prefix/etc/imapd/imapd.conf",

      "$kolab_prefix/etc/kolab/httpd.conf.template" => "$kolab_prefix/etc/apache/apache.conf",
      "$kolab_prefix/etc/kolab/legacy.conf.template" => "$kolab_prefix/etc/apache/legacy.conf",
      "$kolab_prefix/etc/kolab/php.ini.template" => "$kolab_prefix/etc/apache/php.ini",

      "$kolab_prefix/etc/kolab/proftpd.conf.template" => "$kolab_prefix/etc/proftpd/proftpd.conf",

      "$kolab_prefix/etc/kolab/slapd.conf.template" => "$kolab_prefix/etc/openldap/slapd.conf");

   dolog("generating new config");
   
   $ldapuri = URI->new($configdata{'ldap_uri'}) || die "error: could not parse given uri";
   $ldap = Net::LDAP->new($ldapuri->host, port=> $ldapuri->port) || die "could not connect ldap server";
   $ldap->bind($configdata{'bind_dn'}, password=> $configdata{'bind_pw'}) || die "could not bind to ldap";

   $mesg = $ldap->search(base=> "k=kolab,".$configdata{'base_dn'}, scope=> 'base', filter=> "(objectclass=*)");
   $ldapobject = $mesg->pop_entry;
   foreach my $attr ($ldapobject->attributes) {
      $configdata{$attr} = $ldapobject->get_value($attr);
   }
   my $salt = substr $configdata{'proftpd-userPassword'}, 0, 2;
   $configdata{'proftpd-userPassword'} = crypt($configdata{'proftpd-userPassword'}, $salt);

   if ($opt_o) { $configdata{'ldap_uri'} =~ s/7777/389/g; }
   my $dummy = URI->new($configdata{'ldap_uri'});
   $configdata{'ldap_ip'} = $dummy->host;
   $configdata{'ldap_port'} = $dummy->port;

   $configdata{'legacy-mode'} = "# no legacy configuration";
   if ($configdata{'apache-http'} =~ /true/i) {
      $configdata{'legacy-mode'} = "Include \"$kolab_prefix/etc/apache/legacy.conf\"";
   }
   $configdata{'fqdn'} = `hostname -f`;
   chomp($configdata{'fqdn'});

   foreach $key (keys %config_files) {
      build($key, $config_files{$key});
   }
   my $uid = (getpwnam("kolab"))[2];
   my $gid = (getgrnam("kolab"))[2];

   # put together the transport map for postfix
   my $configname="$kolab_prefix/etc/postfix/transport";
   copy($configname, $configname.".old");
   chown($uid,$gid,$configname.".old");
   copy("$kolab_prefix/etc/kolab/transport.template", $configname);
   my $transport = IO::File->new($configname, "a")
        || die "could not write to postfix transport map";
   $mesg = $ldap->search(base=> "k=kolab,".$configdata{'base_dn'}, scope=> 'sub', filter=> "(objectclass=*)")
        || dolog("could not find any transport table entries in ldap");
   if ($mesg->code <= 0) {
      foreach $ldapobject ($mesg->entries) {
         my $routes = $ldapobject->get_value('postfix-transport', asref => 1);
         foreach (@$routes) {
            $_=trim($_);
            defined($opt_v) && print "adding smtp route '$_'\n";
            print $transport $_."\n";
         }
      }
   }
   undef $ldapobject;
   undef $transport;
   system("chown root.root $kolab_prefix/etc/postfix/*"); 
   system("$postmap $kolab_prefix/etc/postfix/transport");
   if (!$opt_o) {
      if (-f $configname.".old") {
        my $rc = `diff -q $configname $configname.old`;
	chomp($rc);
        if ($rc) {
 	   dolog($rc);
           $haschanged{'postfix'}=1;
        }
      } else { $haschanged{'postfix'}=1; }
   }

   my $cyrustemplate = IO::File->new("$kolab_prefix/etc/kolab/cyrus.conf.template","r")
      || die "could not open imapd cyrus.conf template";
   $configname = "$kolab_prefix/etc/imapd/cyrus.conf";
   copy($configname, $configname.".old");
   chown($uid,$gid,$configname.".old");
   $opt_v && printf "creating new $configname from cyrus.conf.template\n";
   my $cyrusconf = IO::File->new($configname,"w") || die "could not open $configname";
   while (<$cyrustemplate>) {
      if (/\@{3}cyrus-imap\@{3}/ && ($configdata{"cyrus-imap"} =~ /true/i)) {
         $_ = "imap cmd=\"imapd -C $kolab_prefix/etc/imapd/imapd.conf\" listen=\"143\" prefork=0\n";
      }
      elsif (/\@{3}cyrus-pop3\@{3}/ && ($configdata{"cyrus-pop3"} =~ /true/i)) {
         $_ = "pop3 cmd=\"pop3d -C $kolab_prefix/etc/imapd/imapd.conf\" listen=\"110\" prefork=0\n";
      }
      elsif (/\@{3}cyrus-imaps\@{3}/ && ($configdata{"cyrus-imaps"} =~ /true/i)) {
         $_ = "imaps cmd=\"imapd -s -C $kolab_prefix/etc/imapd/imapd.conf\" listen=\"993\" prefork=0\n";
      }
      elsif (/\@{3}cyrus-pop3s\@{3}/ && ($configdata{"cyrus-pop3s"} =~ /true/i)) {
         $_ = "pop3s cmd=\"pop3d -s -C $kolab_prefix/etc/imapd/imapd.conf\" listen=\"995\" prefork=0\n";
      }
      elsif (/\@{3}cyrus-sieve\@{3}/ && ($configdata{"cyrus-sieve"} =~ /true/i)) {
         $_ = "sieve cmd=\"timsieved -C $kolab_prefix/etc/imapd/imapd.conf\" listen=\"2000\" prefork=0";
      }
      $_ =~ s/\@{3}.*\@{3}//;
      print $cyrusconf $_;
   }
   undef $cyrustemplate;
   undef $cyrusconf;
   chown($uid,$gid,$configname);
   if (!$opt_o) {
     if (-f $configname.".old") {
        my $rc = `diff -q $configname $configname.old`;
	chomp($rc);
        if ($rc) {
           dolog($rc);
           $haschanged{'imapd'}=1;
        }
     } else { $haschanged{'imapd'}=1; }
   }

   # collect group information from LDAP
   $configname = "$kolab_prefix/etc/imapd/imapd.group";
   copy($configname, $configname.".old");
   chown($uid,$gid,$configname.".old");
   copy("$kolab_prefix/etc/kolab/imapd.group.template", $configname);
   my $groupconf = IO::File->new($configname, "a")
        || die "could not write to $configname";
   my $count = 60000;
   $mesg = $ldap->search(base=> $configdata{'base_dn'}, scope=> 'sub', filter=> '(objectclass=groupofnames)')
        || die "could not query LDAP for group information";
   if ($mesg->code > 0) {
      dolog("warning: could not find groups in LDAP tree");
   } else {
      foreach $ldapobject ($mesg->entries) {
         my $group = $ldapobject->get_value('cn').":*:$count:";
         my $userlist = $ldapobject->get_value('uid', asref => 1);
         foreach (@$userlist) { $group .= "$_,"; }
         $group =~ s/,$//;
         print $groupconf $group."\n";
         $opt_v && printf("added group $group\n");
         $count++;
      }
   }
   undef $ldapobject;
   undef $groupconf;
   chown($uid,$gid,$configname);
   if (!$opt_o) {
      if (-f $configname.".old") {
         my $rc = `diff -q $configname $configname.old`;
         if ($rc) {
           dolog($rc);
           $haschanged{'imapd'}=1;
         }
      } else { $haschanged{'imapd'}=1; }
   } else { return; }

   # open admin channel to local Cyrus IMAP daemon
   my $cyrus = Cyrus::IMAP::Admin->new('localhost')
        || die "could not connect to Cyrus IMAP daemon";
   $cyrus->authenticate('User' => 'manager', 'Password' => $configdata{'bind_pw'}, 
 		        'mechanisms' => "plaintext")
        || die "could not authenticate with Cyrus IMAP daemon ($cyrus->{'error'})";

   # get LDAP user data for checking the mailboxes
   $mesg = $ldap->search(base=> $configdata{'base_dn'}, scope=> 'sub', filter=> '(uid=*)')
        || die "could not query LDAP for all uid's";
   if ($mesg->code > 0) {
      $opt_v && print "warning: could not find uid's in LDAP tree\n";
   } else {
      foreach $ldapobject ($mesg->entries) {
         my $uid = $ldapobject->get_value('mail');
         $uid = trim($uid);
         my $cyruid = "user/".$uid;
         my $deleteflag = $ldapobject->get_value('deleteflag');
         if (defined($deleteflag) && ($deleteflag =~ /true/i)) {
            $opt_v && print "removing mailbox $cyruid\n";
            $cyrus->setaclmailbox($cyruid, 'manager', 'c')
                || dolog("could not reset acl to delete imap user $cyruid");
            $cyrus->deletemailbox($cyruid) || dolog("could not delete imap user $cyruid");
            next;
         }
         my $mailbox = ($cyrus->listmailbox($cyruid))[0];
         if ($uid && ($uid ne "manager") && ($uid ne "freebusy") && ($uid ne "nobody") && !defined($mailbox)) {
            $opt_v && print "create mailbox for user $cyruid\n";
            $cyrus->createmailbox($cyruid)
                || die "could not create Cyrus mailbox for $cyruid ($cyrus->{'error'})";
         }
         my $quota = $ldapobject->get_value('userquota');
         if (defined($quota) && ($quota > 0)) {
            (my $root, my %quota) = $cyrus->quotaroot($cyruid);
            my $setquota = $quota{'STORAGE'}[1];
            if (!defined($setquota) || ($setquota != $quota)) {
               $opt_v && print "resetting quota for user $cyruid to $quota\n";
               $cyrus->setquota($cyruid, 'STORAGE', $quota)
                || die "could not set quota for $cyruid ($cyrus->{'error'})";
            }
         }
      }
   }

   # get shared folder configuration and check it against Cyrus
   $mesg = $ldap->search(base=> $configdata{'base_dn'}, scope=> 'sub', filter=> '(objectclass=sharedfolder)')
        || die "could not qeury LDAP for sharedfolder configuration";
   if ($mesg->code > 0) {
      $opt_v && print "warning: could not find shared folders in LDAP tree\n";
   } else {
      foreach $ldapobject ($mesg->entries) {
         my $folder = $ldapobject->get_value('cn');
         my $deleteflag = $ldapobject->get_value('deleteflag');
         $folder = trim($folder);
         my $cyrfolder = "user.".$folder;
         if (defined($deleteflag) && ($deleteflag =~ /true/i)) {
            $opt_v && print "removing shared folder $cyrfolder\n";
            $cyrus->setacl($cyrfolder, 'manager', 'c')
                || dolog("could not reset acl to delete imap $cyrfolder");
            $cyrus->delete($cyrfolder)
                || dolog("could not delete imap folder $cyrfolder");
            next;
         }
         my $fo = ($cyrus->list($cyrfolder))[0];
         if (!defined($fo)) {
            $opt_v && print "create folder: $cyrfolder\n";
            $cyrus->create($cyrfolder)
                || die "could not create Cyrus shared folder for $cyrfolder ($cyrus->{'error'})";
         }
         my $quota = $ldapobject->get_value('userquota');
         if (defined($quota) && ($quota > 0)) {
            (my $root, my %quota) = $cyrus->quotaroot($cyrfolder);
            my $setquota = $quota{'STORAGE'}[1];
            if (!defined($setquota) || ($setquota != $quota)) {
               $opt_v && print "resetting quota for shared folder $cyrfolder to $quota\n";
               $cyrus->setquota($cyrfolder, 'STORAGE', $quota)
                || die "could not set quota for folder $cyrfolder ($cyrus->{'error'})";
            }
         }
         # first reset current acl
         my @acl = `$kolab_prefix/etc/kolab/workaround.sh $cyrfolder $configdata{'bind_pw'} | sed -e /localhost/d`;
         foreach (@acl) {
            $_ = trim($_);
            (my $user, ) = split / /;
            $opt_v && print "remove acl $user from folder $cyrfolder\n";
            $cyrus->deleteacl($cyrfolder, $user)
                || dolog("could not remove acl from imap folder $cyrfolder ($cyrus->{'error'})");
         }
         #my %acl = $cyrus->listacl($folder) || print "imap folder $folder seems to not have acl\n";
         #foreach my $acl (keys %acl) {
         #   defined($opt_v) && print "remove acl $acl from folder $folder\n";
         #   $cyrus->deleteacl($folder, $acl) || print "could not remove acl from imap folder $folder ($cyrus->{'error'})\n";
         #}
         my $acls = $ldapobject->get_value('acl', asref => 1);
         foreach (@$acls) {
            (my $user, my $acl) = split (/ /,$_,2);
            $user = trim($user);
            $acl = trim($acl);
            $opt_v && print "set $cyrfolder acl to $user $acl\n";
            $cyrus->setacl($cyrfolder, $user, $acl);
         }
      }
   }

   # remove all LDAP objects marked for deletion
   $mesg = $ldap->search(base=> $configdata{'base_dn'}, scope=> 'sub', filter=> '(deleteflag=TRUE)')
        || dolog("could not query LDAP for to be deleted objects");
   if ($mesg->code <= 0) {
      foreach $ldapobject ($mesg->entries) {
         my $dn = $ldapobject->dn;
         $opt_v && print "removing $dn from ldap\n";
         $mesg = $ldap->delete($dn) || dolog("could not delete $dn");
      }
   }

   # find aliases and put together the virtual map for postfix
   # also fill up aliases
   $configname = "$kolab_prefix/etc/postfix/virtual";
   #$configname2 = "$kolab_prefix/etc/postfix/aliases";
   copy("$kolab_prefix/etc/kolab/virtual.template",$configname);
   #copy("$kolab_prefix/etc/kolab/aliases.template",$configname2);
   my $virtual = IO::File->new($configname, "a") || die "could not write to $configname";
   #my $aliasdb = IO::File->new($configname2, "a") || die "could not write to $configname2";
   $mesg = $ldap->search(base=> $configdata{'base_dn'}, scope=> 'sub', filter=> '(mail=*)');
   if ($mesg->code <= 0) {
      foreach $ldapobject ($mesg->entries) {
	 #my $uidval = $ldapobject->get_value('uid');
	 my $mail = $ldapobject->get_value('mail');
         if (defined($mail)) {
            $mail = trim($mail);
            my $aliases = $ldapobject->get_value('alias', asref => 1);
	    #push @$aliases, $ldapobject->get_value('mail');
            foreach (@$aliases) {
               $_ = trim($_);
               my $rule = $_."  ".$mail;
               defined($opt_v) && print "adding virtual entry '$rule'\n";
               print $virtual $rule."\n";
            }
	    #(my $rule, my $dom,) = split(/@/,$mail);
	    #if ($mail) {
	    #   $rule .= ": ".$uidval."@".$dom."\n";
	    #   defined($opt_v) && print "adding aliases entry '$rule'\n";
	    #   print $aliasdb $rule;
            #}
         }
      }
   } elsif ($opt_v) { print "warning: could not find any aliases in ldap\n"; }
   $virtual->close;
   #$aliasdb->close;
   system("chown root.root $kolab_prefix/etc/postfix/*"); 
   system("$postmap $configname");
   #system("$newaliases");
   if (!$opt_o && -f $configname.old) {
      my $rc = `diff -q $configname $configname.old`;
      if ($rc) {
         dolog($rc);
         $haschanged{'postfix'}=1;
      }
   }

   $ldap->unbind;
   dolog("done generating new kolab config");
}

sub kolab_reload
{
   # trigger server config reload
   if ($haschanged{'slapd'}) { 
      dolog("restarting openldap");
      system("$kolab_prefix/etc/rc.d/rc.openldap restart");
   }
   if($haschanged{'saslauthd'}) {
      dolog("restarting saslauthd");
      system("$kolab_prefix/etc/rc.d/rc.sasl stop; sleep 1; $kolab_prefix/sbin/saslauthd -a ldap -n 5");
   }
   if ($haschanged{'apache'}) {
      dolog("reloading apache");
      system("$kolab_prefix/sbin/apachectl graceful"); 
   }
   if ($haschanged{'postfix'}) {
      dolog("reloading postfix");
      system("$kolab_prefix/sbin/postfix reload"); 
   }
   if ($haschanged{'imapd'}) {
      dolog("restarting imapd");
      system("$kolab_prefix/etc/rc.d/rc.imapd restart"); 
   }
   if ($configdata{'proftpd-ftp'} =~ /true/i) {
      dolog("make sure proftpd is running");
      system("$kolab_prefix/etc/rc.d/rc.proftpd start");
      if ($haschanged{'proftpd'}) {
         dolog("reloading proftpd");
         kill("SIGHUP",`cat $kolab_prefix/var/proftpd/proftpd.pid`); 
      }
   } else {
      dolog("make sure proftpd isn't running");
      system("$kolab_prefix/etc/rc.d/rc.proftpd stop");
   }
}


################
# MAIN PROGRAM #
################

openlog("kolab", 'cons, pid', 'user');
my $pidfile = IO::File->new("$kolab_prefix/var/kolab/kolab.pid", "w+")
        || die "could not open pid file";
print $pidfile $$;
undef $pidfile;

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

dolog("kolab initialization starts");
configchange();
$opt_o && exit(0);

kolab_reload();
dolog("kolab started");

my $request;
my $response;
my $pdu;
my $changes = 0;

my $kolab_port = 9999;
$server = IO::Socket::INET->new(
    LocalPort => $kolab_port, Proto => "tcp", ReuseAddr =>1, Type => SOCK_STREAM, LocalAddr => "127.0.0.1", Listen => 10)
    || die "Couldn't be a tcp server on port $kolab_port : $@\n";

dolog("waiting for incoming connection");

while ($conn = $server->accept()) {
 
   dolog("got incoming connection");
   my $select = IO::Select->new($conn);

   while ($conn) {

      undef $pdu;
      my $ready;
      my $offset = 0;

      # we only trigger the config activation if no ldap requests are pending
      if (!($select->can_read(1)) && $changes) {
         configchange($changes);
         kolab_reload();
         %haschanged = ();
         $changes = 0;
      }

      dolog("waiting for ldap updates ...");
      for( $ready = 1 ; $ready ; $ready = $select->can_read(1)) {
         $offset = asn_read($conn, $pdu, $offset);
         defined($offset) or $offset = 0;
	 sleep 1
      }

      if ($pdu) { 
         #debug_request($pdu); 
         $request = $LDAPRequest->decode($pdu) || die $LDAPRequest->error;
         $_ = get_request_type($request);
         undef $pdu;
         debug("got $_");

         SWITCH: {
            if (/bindRequest/) { $pdu = bind_response($request); last SWITCH; }
            if (/addRequest/) { $pdu = add_response($request); $changes = 1; last SWITCH; }
            if (/delRequest/) { $pdu = del_response($request); $changes = 1; last SWITCH; }
            if (/modifyRequest/) { $pdu = mod_response($request); $changes = 1; last SWITCH; }
            if (/modDNRequest/) { $pdu = moddn_response($request); $changes = 1; last SWITCH; }

            if (/unbindRequest/) {
               debug("Got unbindRequest");
            } else {
               debug("Unhandled Request!");
            #$pdu = $LDAPResult->encode(
            #          resultCode => Net::LDAP::Constant::LDAP_PROTOCOL_ERROR, 
            #          matchedDN => '', errorMessage => "Invald Request received")
            #   || die $LDAPRequest->error;
            #asn_send($conn,$pdu,0);
            #undef $pdu;
            }
            #$select->remove($conn);
            $conn->close;
            undef $conn;
         } # SWITCH
      } # if pdu

      if ($pdu) {
         syswrite($conn, $pdu, length($pdu));
         $changes && dolog("config change detected");
         #debug_response($pdu);
         $response = $LDAPResponse->decode($pdu) || die $LDAPResponse->error;
      }
      if ($reinit) {
         $conn->close;
         undef $conn;
         $reinit=0;
      }
   } # while conn
}
$server->close;
exit 0;
