#!/usr/bin/perl
package mailcyrus;

# Name: IAM, Interactive Account Manager
# Description: Multi server Multi domain account manager
# Version: 0.8
# Authors: Jason Jorgensen <jasonj@innominatus.com>
# Copyright (C) 2003  Jason Jorgensen
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
###########################################################################



# This is the standard account type module, adds a simple username and password to a mysql table.
#

## User defined variables
#############################################################
my $driver = "mysql";
my $database = "CYRUS";
my $hostname = "localhost";
my $port = "3306";
my $user = "iam";
my $password = "iammasterpassword";
my $table = "USERDATA";
my $uniqueidfield = "UNIQUEID";
my $usernamefield = "USERNAME";
my $passwordfield = "USERPASSWORD";

my $imaphostname = "mail.domain.com";
my $imapport = "143";
my $imaplogin = "cyrus";
my $imappassword = "cyruspassword";
my $imapquotasize = "10000";
#############################################################

use DBIx::Broker;
#use strict;

BEGIN {
    require Exporter;
    use vars qw( $VERSION @ISA );
	$VERSION     = (split ' ', q!$Revision: 0.6 $!)[1];
    @ISA          =  qw( Exporter );
}

sub new {
  my $class = shift @_;
  my $self = {};
  
  $self->{'authdb'} = eval {DBIx::Broker->new("$driver", "$database", "$hostname", "$port", "$user", "$password")};
  if ( ! $self->{'authdb'} ) { return "0 Could not open database $database!\n";
  }    
  bless ( $self, $class );
}

sub close {
  my $self = shift @_;

  eval { $self->{'authdb'}->disconnect(); }
}

sub debug {
  my $self = shift @_;

  eval {$self->{'authdb'}->debug_on( \*STDERR )};  # put DBIx::Broker into DEBUG mode
  &check_for_errors($@);
  $self->{'debug'} = 1;
}

sub add_fields {
  my $self = shift @_;
  
  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>0
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>0
                        name=>password,desc=>User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>cryptedpassword,desc=>Already Crypted User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>quota,desc=>Mailbox size,minlen=>4,maxlen=>64,optional=>1\n";
  return "0 ".$requiredfields;
}

sub add {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  my $password = shift @_;
  my $cryptedpassword = shift @_;
  my $quota = shift @_;

  my @errors;
  my $error;
  
  use IMAP::Admin;
  
  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }
  if (! $password && ! $cryptedpassword) { push @errors, 102; }

  if (eval {$self->{'authdb'}->select_one_value($usernamefield, $table, "WHERE $usernamefield='$username'", 0)}) { push @errors, 200; }

  if ($#errors < 0) {
    if ($quota) { $imapquotasize = $quota; }
  
    if ($password) { 
      use Crypt::PasswdMD5;
      use String::Random;
      $rand = new String::Random;
      $salt = $rand->randpattern("ssssssss");
      
      $finalpassword = unix_md5_crypt($password, $salt); 
    }
    elsif ($cryptedpassword) { $finalpassword = $cryptedpassword; }

    my %data = ("$uniqueidfield", $uniqueid,
                "$usernamefield", $username,
                "$passwordfield", $finalpassword);
    eval {$self->{'authdb'}->insert("$table", \%data)};
    
    print "DEBUG: calling imapadmin with: $imaphostname, $imapport, $imaplogin, $imappassword\n" if $self->{'debug'};
    $imap = eval {IMAP::Admin->new('Server'   => $imaphostname,
                                   'Port'     => $imapport,
                                   'Login'    => $imaplogin,
                                   'Password' => $imappassword)};
    ## Create cyrus user account
    if ($err != 0 ) { return "1 Failed to connect to IMAP server\n"; }                                   
    eval{$imap->create("user.$username")};
    &check_for_errors($@);
    if ($err != 0) { $error .= "1 Error creating Cyrus IMAP User: $imap->{'Error'}\n"; }

    ## create user INBOX
    eval{$imap->create("user.$username.INBOX")};
    &check_for_errors($@);
    if ($err != 0) { $error .= "1 Error creating Cyrus .INBOX: $imap->{'Error'}\n"; }

    ## set user quota
    eval{$imap->set_quota("user.$username", $imapquotasize)};
    &check_for_errors($@);
    if ($err != 0) { $error .= "1 Error Setting Quota .INBOX: $imap->{'Error'}\n"; }

    ## set user permissions
    eval{$imap->set_acl("user.$username", "$username", "lrswipcda", "cyrus", "cd")};
    &check_for_errors($@);
    if ($err != 0) { $error .= "1 Error Setting ACL .INBOX: $imap->{'Error'}\n"; }

    if ($error) {
      $result = $error;
      $imap->delete("user.$username");
    }
    else {
      $result = "0 $imaphostname";
    }
    $imap->close;
    return $result;
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub modify_fields {
  my $self = shift @_;
  
  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>0
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>0
                        name=>newuniqueid,desc=>New Unique Identifier,minlen=>0,maxlen=>64,optional=>1
                        name=>newusername,desc=>New Username,minlen=>4,maxlen=>64,optional=>1
                        name=>newpassword,desc=>New User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>newcryptedpassword,desc=>Already Crypted User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>quota,desc=>Mailbox size,minlen=>4,maxlen=>64,optional=>1\n";
  return "0 ".$requiredfields;
}

sub modify {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  my $newuniqueid = shift @_;
  my $newusername = shift @_;
  my $newpassword = shift @_;
  my $newcryptedpassword = shift @_;
  my $quota = shift @_;

  my @errors;
  
  use IMAP::Admin;

  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  @allusernames = eval {$self->{'authdb'}->select_one_column($usernamefield, $table, "", 0)};
  if (! grep /^$username$/, @allusernames) { push @errors, 201; }
  if (grep /^$newusername$/, @allusernames) { push @errors, 200; }

  if ($#errors < 0) {
    if ($newpassword) { 
      use String::Random;
      $rand = new String::Random;
      $salt = $rand->randpattern("ss");

      $finalpassword = crypt($newpassword, $salt); 
    }
    elsif ($newcryptedpassword) { $finalpassword = $newcryptedpassword; }

    my %data;
    if ($newuniqueid) { $data{"$uniqueidfield"} = $newuniqueid; }
    if ($newusername) { 
      $imap = eval {IMAP::Admin->new('Server'   => $imaphostname,
                                     'Port'     => $imapport,
                                     'Login'    => $imaplogin,
                                     'Password' => $imappassword)};
      $imap->delete("user.$username");

      eval{$imap->create("user.$newusername")};
      &check_for_errors($@);
      if ($err != 0) { $error .= "1 Error creating Cyrus IMAP User: $imap->{'Error'}\n"; }

      ## create user INBOX
      eval{$imap->create("user.$newusername.INBOX")};
      &check_for_errors($@);
      if ($err != 0) { $error .= "1 Error creating Cyrus .INBOX: $imap->{'Error'}\n"; }
  
      ## set user quota
      eval{$imap->set_quota("user.$newusername", $imapquotasize)};
      &check_for_errors($@);
      if ($err != 0) { $error .= "1 Error Setting Quota .INBOX: $imap->{'Error'}\n"; }
  
      ## set user permissions
      eval{$imap->set_acl("user.$newusername", "$newusername", "lrswipcda", "cyrus", "cd")};
      &check_for_errors($@);
      if ($err != 0) { $error .= "1 Error Setting ACL .INBOX: $imap->{'Error'}\n"; }
  
      if ($error) {
        $result = $error;
        $imap->delete("user.$newusername");
      }
      $imap->close;

      $data{"$usernamefield"} = $newusername; 
    }
    if ($newpassword) { $data{"$passwordfield"} = $finalpassword; }
    if (%data) { eval {$self->{'authdb'}->update("$table", \%data, "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'")}; }
    &check_for_errors($self, $@);
    return 0;
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub delete_fields {
  my $self = shift @_;
  
  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>1
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>1
                        name=>password,desc=>User Password,minlen=>4,maxlen=>64,optional=>1\n";
  return "0 ".$requiredfields;
}

sub delete {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  
  my @errors;

  use IMAP::Admin;
  
  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  @allusernames = eval {$self->{'authdb'}->select_one_column($usernamefield, $table, "", 0)};
  if (! grep /^$username$/, @allusernames) { push @errors, 201; }

  if ($#errors < 0) {
    eval {$self->{'authdb'}->delete("$table", "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'")};
    &check_for_errors($self, $@);

    $imap = eval {IMAP::Admin->new('Server'   => $imaphostname,
                                   'Port'     => $imapport,
                                   'Login'    => $imaplogin,
                                   'Password' => $imappassword)};
    $imap->delete("user.$username");
    $imap->close;
    return 0;
  }
  else {
    return &display_errors($self, \@errors);
  }
  
}

sub display {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  
  my @errors;

  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  my @fields = (
                "$usernamefield",
                "$passwordfield",
               );
  
  $accountinfo = eval {$self->{'authdb'}->select_one_row(\@fields, $table, "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'", 1)};
  &check_for_errors($self, $@);
  my $result = "$usernamefield=>$accountinfo->{$usernamefield},$passwordfield=>$accountinfo->{$passwordfield}";

  if ($#errors < 0) {
    return "0 ".$result;
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub check_for_errors {
  my $self = shift @_;
  $error = shift @_;

  if ($error) {
    print STDERR "Error: $error\n";
    return "1 Unknown error occured, inform administrator\n";
  }                                                                                                                                        
}

sub display_errors {
  my $self = shift @_;
  my @errors = @{shift @_};
  my $errmsg;

  foreach $error (@errors) {
    $errmsg .= "$error ";
    if ($error == 666) { print "Yet to be classified error"; }
    
    elsif ($error == 100) { $errmsg .= "Must specify a 'uniqueid'"; }
    elsif ($error == 101) { $errmsg .= "Must specify a 'username'"; }
    elsif ($error == 102) { $errmsg .= "Must specify a 'password'"; }
    
    elsif ($error == 200) { $errmsg .= "Duplicate Username!"; }
    elsif ($error == 201) { $errmsg .= "Username doesnt exist!"; }

    elsif ($error == 1501) { $errmsg .= "Must specify a type when calling function 'singledb'"; }
    
    else { $errmsg .= "Unknown error"; }
    $errmsg .= "\n";
  }
  return $errmsg;
}


1;

__END__