# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe 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.
#
# Extmail - a high-performance webmail to maildir
# $Id$
use strict;
use DBI;

package Ext::Auth::MySQL;
use Exporter;
use Ext::Passwd;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(auth);

sub new {
    my $this = shift;
    my $self = bless {@_}, ref $this || $this;
    $self->init(@_);
    $self;
}

sub init {
    my $self = shift;
    my %opt = @_;

    $opt{host} = '127.0.0.1' if not defined $opt{host};
    $opt{dbname} = 'extmail_db' if not defined $opt{dbname};
    $opt{dbuser} = 'root' if not defined $opt{dbuser};
    $opt{dbpw} = 'password' if not defined $opt{dbpw};

    $self->{opt}=\%opt;

    my $connect = "DBI:mysql:database=$opt{dbname};host=$opt{host}";
    if ($opt{socket}) {
        $connect .= ";mysql_socket=$opt{socket}";
    }
    my $dbh = DBI->connect(
        $connect,$opt{dbuser}, $opt{dbpw}, {'RaiseError' => 1}
    );

    $self->{dbh} = $dbh;
    $self->{pwhandle} = Ext::Passwd->new(
        fallback_scheme => $opt{crypt_type} || 'crypt'
    ); # default type
}

# XXX meaningful for MySQL driver only
sub build_sql {
    my $self = shift;
    my $schema = $self->{opt}->{'schema'};
    my @params = @_;
    my $username = $self->{opt}->{'table_attr_username'};
    my $sql;

    if ($schema eq 'vpopmail1') {
        my $domain = $self->{opt}->{'table_attr_domain'};
        $sql  = "SELECT * FROM $self->{opt}->{table} WHERE ";
        $sql .= "$username='$params[0]' AND $domain='$params[1]'";
    } elsif ($schema eq 'vpopmail2') {
        my $table_name = $params[1];
        $table_name =~ s![-\.]!_!g;
        $sql  = "SELECT * FROM $table_name WHERE ";
        $sql .= "$username='$params[0]'";
    } else {
        $sql = "SELECT * FROM $self->{opt}->{table} WHERE $username='$params[0]'";
    }
    $sql;
}

sub search {
    my $self = shift;
    my %res = ();
    my $username = $self->{opt}->{'table_attr_username'};
    my $SQL = $self->build_sql(@_);
    my $sth = $self->{dbh}->prepare($SQL);

    $sth->execute();
    while(my $r=$sth->fetchrow_hashref()) {
        $res{$r->{$username}} = $r; # feedback all rows
    };
    $sth->finish();
    \%res; # return a REF
}

# return value redifination since 0.24-RC2
#
# $rv =  0  LOGIN_OK
# $rv = -1  LOGIN_FAIL
# $rv =  1  LOGIN_DISABLED
# $rv =  2  LOGIN_DEACTIVE
# $rv =  3  LOGIN_EXPIRED
sub auth {
    my $self = shift;
    my ($username, $password) = (@_);
    my $res;
    my $schema = $self->{opt}->{'schema'};

    if ($schema =~ m!^vpopmail!) {
        $username =~ /([^\@]+)@([^\@]+)/;
        $res = $self->search($1, $2);
        $username = $1; # XXX vpopmail
    } else {
        $res = $self->search($username); # XXX virtual
    }

    if(scalar keys %$res) {
        my $pwd = $res->{$username}->{$self->{opt}->{'table_attr_passwd'}};
        my $rv = -1; # flag to indicate authentication fail/ok/disabled
        my $handle = $self->{pwhandle}; # Ext::Passwd object

        # this step is a must, or null userpassword record will cause hole
        # that anonymous can step in the system
        return -1 unless($password && $pwd);

        if ($handle->verify($password, $pwd)) {
            if ($self->{opt}->{'table_attr_disablewebmail'} &&
                $res->{$username}->{$self->{opt}->{'table_attr_disablewebmail'}}) {
                return ($rv = 1);
            }
            if ($self->{opt}->{'table_attr_active'} &&
                !$res->{$username}->{$self->{opt}->{'table_attr_active'}}) {
                return ($rv = 2);
            }
            $self->{INFO} = $self->_fill_user_info($res->{$username});
            return 0;
        }else {
            return -1;
        }
    }

    -1; # default to fail
}

sub change_passwd {
    my $self = shift;
    my ($username, $old, $new) = @_;

    # verify old password
    if($self->auth($username, $old) == 0) {
        # encrypt new password and update it
        my $handle = $self->{pwhandle};
        my $type = $handle->{_scheme};

        my $crypted_new = $handle->encrypt($type, $new);

        my $table = $self->{opt}->{table};
        my $schema = $self->{opt}->{'schema'};

        if ($schema =~ /^vpopmail/) {
            $username =~ /([^\@]+)@([^\@]+)/;
            $username = $1; # XXX vpopmail style
            if ($schema eq 'vpopmail2') {
                $table = $2;
                $table =~ s![-\.]!_!g;
            }
        }
        my $attr_pw = $self->{opt}->{table_attr_passwd};
        my $clearpw = $self->{opt}->{table_attr_clearpw};
        my $attr_un = $self->{opt}->{table_attr_username};

        my $SQL = "UPDATE $table set $attr_pw='$crypted_new'";
        if ($clearpw) {
            $SQL .= ",$clearpw='$new'";
        }
        $SQL .= " WHERE $attr_un='$username'";

        my $sth = $self->{dbh}->prepare($SQL);
        $sth->execute;
        $sth->finish();
        return 1;
    }else {
        return 0;
    }
}

sub _fill_user_info {
    my $self = shift;
    my $opt = $self->{opt};
    my $entry = $_[0];
    my %info = ();

    # original infomation filling
    foreach my $key (keys %$entry) {
        $info{$key} = $entry->{$key};
    }

    # compatible with ExtMail ldap version
    $info{QUOTA} = $info{$opt->{'table_attr_quota'}};
    $info{NETDISKQUOTA} = $info{$opt->{'table_attr_netdiskquota'}};
    $info{HOME} = $info{$opt->{'table_attr_home'}}; # must exists
    $info{MAILDIR} = $info{$opt->{'table_attr_maildir'}} || "$info{HOME}/Maildir";

    if ($info{$opt->{'table_attr_disablenetdisk'}}) {
        $info{OPTIONS} = 'disablenetdisk';
    }
    if ($info{$opt->{'table_attr_disablepwdchange'}}) {
        $info{OPTIONS} = ($info{OPTIONS} ? $info{OPTIONS}.',' : '') .'disablepwdchange';
    }
    \%info;
}

sub DESTORY {
    my $self = shift;
    $self->{dbh}->disconnect();
    undef $self;
}
1;


syntax highlighted by Code2HTML, v. 0.9.1