# 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. # # ExtMan - web interface to manage virtual accounts # $Id$ use strict; use DBI; package Ext::Mgr::MySQL; use Exporter; use Ext::Mgr; use POSIX qw(strftime); use vars qw(@ISA @EXPORT); @ISA = qw(Exporter Ext::Mgr); @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' => 0} ); $self->{dbh} = $dbh; $self->{psize} = $opt{psize} || 10; $self->{crypt_type} = $opt{crypt_type} || 'crypt'; # default type } sub search { my $self = shift; my $filter = $_[0]; my %res = (); my $username = $self->{opt}->{'table_attr_username'}; my $SQL = "SELECT * FROM $self->{opt}->{table} WHERE $username='$filter'"; 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 } sub auth { my $self = shift; my ($username, $password) = (@_); my $res = $self->search($username); if(scalar keys %$res) { my $pwd = $res->{$username}->{$self->{opt}->{'table_attr_passwd'}}; # this step is a must, or null userpassword record will cause hole # that anonymous can step in the system return 0 unless($password && $pwd); if($self->verify($password, $pwd)) { $self->{INFO} = $self->_fill_user_info($res->{$username}); return 1; }else { return 0; } } 0; # default ?:) } sub change_passwd { my $self = shift; my ($username, $old, $new) = @_; # verify old password if($self->auth($username, $old)) { # encrypt new password and update it my $crypted_new = $self->encrypt($self->{crypt_type}, $new); my $table = $self->{opt}->{table}; 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}; } $info{TYPE} = $info{'type'}; \%info; } sub get_entry { my $self = shift; my $sth = $self->{dbh}->prepare($_[0]); $sth->execute(); $sth->fetchrow_hashref(); # the first entry if multiplies return } sub get_entries { my $self = shift; my $sth = $self->{dbh}->prepare($_[0]); my $arr = []; $sth->execute(); while (my $r=$sth->fetchrow_hashref()) { push @$arr, $r; } $arr; } #==========================# # extmailUser land handler # #==========================# sub by_domain { lc $a->{domain} cmp lc $b->{domain}; } sub by_username { lc $a->{username} cmp lc $b->{username}; } sub by_alias { lc $a->{address} cmp lc $b->{address}; } sub by_manager { lc $a->{username} cmp lc $b->{username}; } sub get_users_list { my $self = shift; my $SQL = "SELECT * FROM mailbox WHERE domain='$_[0]'"; my $rs = $self->get_entries($SQL); my $arr = []; # null ARRAY ref foreach my $ref (sort by_username @$rs) { push @$arr, { mail => $ref->{username}, cn => $ref->{name}, domain => $ref->{domain}, uidnumber => $ref->{uidnumber}, gidnumber => $ref->{gidnumber}, uid => $ref->{uid}, netdiskquota => $ref->{netdiskquota}, active => $ref->{active} ? 1 : 0, quota => $ref->{quota}, passwd => $ref->{password}, clearpw => $ref->{clearpwd}, mailhost => $ref->{mailhost}, maildir => $ref->{maildir}, homedir => $ref->{homedir}, expire => $ref->{expiredate}, create => $ref->{createdate}, disablepwdchange => $ref->{disablepwdchange} ? 1 : 0, disablesmtpd => $ref->{disablesmtpd}, disablesmtp => $ref->{disablesmtp}, disablewebmail => $ref->{disablewebmail}, disablenetdisk => $ref->{disablenetdisk}, disableimap => $ref->{disableimap}, disablepop3 => $ref->{disablepop3}, } } scalar @$arr ? $arr : undef; } sub get_domains_list { my $self = shift; my $SQL = 'SELECT * FROM domain'; my $rs = $self->get_entries($SQL); my $arr = []; foreach my $ref ( sort by_domain @$rs ) { push @$arr, { domain => $ref->{domain}, create => $ref->{createdate}, expire => $ref->{expiredate}, description => $ref->{description}, hashdirpath => $ref->{hashdirpath}, maxalias => $ref->{maxalias}, maxusers => $ref->{maxusers}, maxquota => $ref->{maxquota}, maxndquota => $ref->{maxnetdiskquota}, transport => $ref->{transport}, can_signup => $ref->{can_signup}, default_quota => $ref->{default_quota}, default_ndquota => $ref->{default_netdiskquota}, default_expire => $ref->{default_expire}, disablesmtpd => $ref->{disablesmtpd}, disablesmtp => $ref->{disablesmtp}, disablewebmail => $ref->{disablewebmail}, disablenetdisk => $ref->{disablenetdisk}, disableimap => $ref->{disableimap}, disablepop3 => $ref->{disablepop3}, active => $ref->{active} ? 1 : 0, } } scalar @$arr ? $arr : undef; } sub get_aliases_list { my $self = shift; my $SQL = "SELECT * FROM alias where domain='$_[0]'"; my $rs = $self->get_entries($SQL); my $arr = []; foreach my $ref ( sort by_alias @$rs ) { my $goto = $ref->{goto}; # XXX push @$arr, { alias => $ref->{address}, domain => $ref->{domain}, goto => ($goto =~ m!,!) ? [split(/,/,$goto)] : $goto, active => $ref->{active} ? 1 : 0, create => $ref->{createdate}, expire => $ref->{expiredate}, } } scalar @$arr ? $arr : undef; } sub get_managers_list { my $self = shift; my $SQL = 'SELECT * FROM manager'; my $rs = $self->get_entries($SQL); my $arr = []; foreach my $ref (sort by_manager @$rs) { push @$arr, { manager => $ref->{username}, cn => $ref->{name}, question => $ref->{question}, answer => $ref->{answer}, disablepwdchange => $ref->{disablepwdchange}, create => $ref->{createdate}, expire => $ref->{expiredate}, type => $ref->{type}, passwd => $ref->{password}, active => $ref->{active} ? 1 : 0, } } scalar @$arr ? $arr : undef; } sub add_user { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $ctype = $self->{crypt_type}; my $passwd = $self->encrypt($ctype, $opt{passwd}); my $clearpw = ($self->{opt}->{'table_attr_clearpw'} ? $opt{passwd} : ''); my $active = $opt{active} ? 1 : 0; $db->do("INSERT into mailbox( username, uid, password, clearpwd, name, mailhost, maildir, homedir, quota, netdiskquota, domain, uidnumber, gidnumber, createdate, expiredate, active, disablepwdchange, disablesmtpd, disablesmtp, disablewebmail, disablenetdisk, disableimap, disablepop3) VALUES( '$opt{mail}', '$opt{uid}', '$passwd', '$clearpw', '$opt{cn}', '$opt{mailhost}', '$opt{maildir}', '$opt{homedir}', '$opt{quota}', '$opt{netdiskquota}', '$opt{domain}', '$opt{uidnumber}', '$opt{gidnumber}', '$opt{create}', '$opt{expire}', '$active', '$opt{disablepwdchange}', '$opt{disablesmtpd}', '$opt{disablesmtp}', '$opt{disablewebmail}', '$opt{disablenetdisk}', '$opt{disableimap}', '$opt{disablepop3}' )" ); if ($db->err) { return $db->errstr; } else { return 0; } } sub add_domain { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $active = $opt{active} ? 1 : 0; $db->do("INSERT into domain( domain, description, hashdirpath, maxalias, maxusers, maxquota, maxnetdiskquota, transport, can_signup, default_quota, default_netdiskquota, default_expire, disablesmtpd, disablesmtp, disablewebmail, disablenetdisk, disableimap, disablepop3, createdate, expiredate, active) VALUES( '$opt{domain}', '$opt{description}', '$opt{hashdirpath}', '$opt{maxalias}', '$opt{maxusers}', '$opt{maxquota}', '$opt{maxndquota}', '$opt{transport}', '$opt{can_signup}', '$opt{default_quota}', '$opt{default_ndquota}', '$opt{default_expire}', '$opt{disablesmtpd}', '$opt{disablesmtp}', '$opt{disablewebmail}', '$opt{disablenetdisk}', '$opt{disableimap}', '$opt{disablepop3}', '$opt{create}', '$opt{expire}', '$active' )" ); if ($db->err) { return $db->errstr; } else { return 0; } } sub add_alias { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $active = $opt{active} ? 1 : 0; my $goto = join(',', split(/\n/, $opt{goto})); $db->do("INSERT into alias( address, goto, domain, createdate, expiredate, active) VALUES( '$opt{alias}', '$goto', '$opt{domain}', '$opt{create}', '$opt{expire}', '$active' )" ); if ($db->err) { return $db->errstr; } else { return 0; } } sub add_manager { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $ctype = $self->{crypt_type}; my $passwd = $self->encrypt($ctype, $opt{passwd}); my $active = $opt{active} ? 1 : 0; $db->do("INSERT into manager( username, password, type, uid, name, question, answer, disablepwdchange, createdate, expiredate, active) VALUES( '$opt{manager}', '$passwd', '$opt{type}', '', '$opt{cn}', '$opt{question}', '$opt{answer}', '$opt{disablepwdchange}', '$opt{create}', '$opt{expire}', '$active' )" ); return $db->errstr if ($db->err); foreach my $vd (split(/\s+/, $self->mshack($opt{domain}))) { $db->do("INSERT into domain_manager( username, domain, createdate, active) VALUES( '$opt{manager}', '$vd', '$opt{create}', '1' )" ); return $db->errstr if ($db->err); } 0; # success } sub delete_user { my $self = shift; my $user = $_[0]; my $db = $self->{dbh}; my ($domain) = ($user =~ m!.*@(.*)!); $db->do("DELETE FROM mailbox where username='$user'"); if ($db->err) { return $db->errstr; } else { return 0; } } sub delete_alias { my $self = shift; my $db = $self->{dbh}; $db->do("DELETE FROM alias where address='$_[0]'"); if ($db->err) { return $db->errstr; } else { return 0; } } sub delete_domain { my $self = shift; my $db = $self->{dbh}; $db->do("DELETE FROM domain where domain='$_[0]'"); if ($db->err) { return $db->errstr; } else { return 0; } } sub delete_manager { my $self = shift; my $db = $self->{dbh}; $db->do("DELETE FROM manager WHERE username='$_[0]'"); return $db->errstr if ($db->err); $db->do("DELETE FROM domain_manager WHERE username='$_[0]'"); if ($db->err) { return $db->errstr; } else { return 0; } } sub modify_user { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $active = $opt{active} ? 1 : 0; if ($opt{passwd}) { my $ctype = $self->{crypt_type}; my $passwd = $self->encrypt($ctype, $opt{passwd}); my $SQL = "UPDATE mailbox set password='$passwd'"; if ($self->{opt}->{'table_attr_clearpw'}) { $SQL .= ",clearpwd='$opt{passwd}'"; } $SQL .=" WHERE username='$opt{user}'"; $db->do($SQL); return $db->errstr if ($db->err); } $db->do("UPDATE mailbox set name='$opt{cn}', quota='$opt{quota}', netdiskquota='$opt{netdiskquota}', uidnumber='$opt{uidnumber}', gidnumber='$opt{gidnumber}', expiredate='$opt{expire}', active='$active', disablepwdchange='$opt{disablepwdchange}', disablesmtpd='$opt{disablesmtpd}', disablesmtp='$opt{disablesmtp}', disablewebmail='$opt{disablewebmail}', disablenetdisk='$opt{disablenetdisk}', disableimap='$opt{disableimap}', disablepop3='$opt{disablepop3}' WHERE username='$opt{user}'" ); if ($db->err) { return $db->errstr; } else { return 0; } } sub modify_alias { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $active = $opt{active} ? 1 : 0; my $goto = join(',', split(/\n/, $opt{goto})); $db->do("UPDATE alias set goto='$goto', expiredate='$opt{expire}', active='$active' WHERE address='$opt{alias}'" ); if ($db->err) { return $db->errstr; } else { return 0; } } sub modify_domain { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $active = $opt{active} ? 1 : 0; $db->do("UPDATE domain set maxusers='$opt{maxusers}', maxalias='$opt{maxalias}', maxquota='$opt{maxquota}', maxnetdiskquota='$opt{maxndquota}', transport='$opt{transport}', can_signup='$opt{can_signup}', default_quota='$opt{default_quota}', default_netdiskquota='$opt{default_ndquota}', default_expire='$opt{default_expire}', disablesmtpd='$opt{disablesmtpd}', disablesmtp='$opt{disablesmtp}', disablewebmail='$opt{disablewebmail}', disablenetdisk='$opt{disablenetdisk}', disableimap='$opt{disableimap}', disablepop3='$opt{disablepop3}', expiredate='$opt{expire}', active='$active', description='$opt{description}' WHERE domain='$opt{domain}'" ); if ($db->err) { return $db->errstr; } else { return 0; } } sub modify_manager { my $self = shift; my %opt = @_; my $db = $self->{dbh}; my $time = strftime("%Y-%m-%d %H:%M:%S", localtime); # update password if set if ($opt{passwd}) { my $ctype = $self->{crypt_type}; my $passwd = $self->encrypt($ctype, $opt{passwd}); $db->do("UPDATE manager set password='$passwd' WHERE username='$opt{manager}'"); return $db->errstr if ($db->err); } # update main information my $active = $opt{active} ? 1 : 0; $db->do("UPDATE manager set name='$opt{cn}', question='$opt{question}', answer='$opt{answer}', disablepwdchange='$opt{disablepwdchange}', expiredate='$opt{expire}', active='$active' WHERE username='$opt{manager}'" ); return $db->errstr if ($db->err); # delete old owndomain, to simplify procedure $db->do("DELETE FROM domain_manager where username='$opt{manager}'"); return $db->errstr if ($db->err); # add new owndomain foreach my $vd (split(/\s+/, $self->mshack($opt{domain}))) { $db->do("INSERT into domain_manager (username, domain, createdate, active) VALUES( '$opt{manager}', '$vd', '$time', '1' )" ); # ignore errstr even with some err, sucks } if ($db->err) { return $db->errstr; } else { return 0; } } sub get_user_info { my $self = shift; my $user = $_[0]; my $domain = $user; $domain =~ s#^([^\@]+)@##; my $SQL = "SELECT * FROM mailbox where username='$user'"; my $ref = $self->get_entry($SQL); return undef unless ($ref); return { mail => $ref->{username}, cn => $ref->{name}, domain => $ref->{domain}, uidnumber => $ref->{uidnumber}, gidnumber => $ref->{gidnumber}, uid => $ref->{uid}, netdiskquota => $ref->{netdiskquota}, active => $ref->{active} ? 1 : 0, quota => $ref->{quota}, passwd => $ref->{password}, clearpw => $ref->{clearpwd}, mailhost => $ref->{mailhost}, maildir => $ref->{maildir}, homedir => $ref->{homedir}, expire => $ref->{expiredate}, create => $ref->{createdate}, disablepwdchange => $ref->{disablepwdchange} ? 1 : 0, disablesmtpd => $ref->{disablesmtpd}, disablesmtp => $ref->{disablesmtp}, disablewebmail => $ref->{disablewebmail}, disablenetdisk => $ref->{disablenetdisk}, disableimap => $ref->{disableimap}, disablepop3 => $ref->{disablepop3}, } } sub get_domain_info { my $self = shift; my $SQL = "SELECT * FROM domain WHERE domain='$_[0]'"; my $ref = $self->get_entry($SQL); return undef unless ($ref); return { domain => $ref->{domain}, create => $ref->{createdate}, expire => $ref->{expiredate}, description => $ref->{description}, hashdirpath => $ref->{hashdirpath}, maxalias => $ref->{maxalias}, maxusers => $ref->{maxusers}, maxquota => $ref->{maxquota}, maxndquota => $ref->{maxnetdiskquota}, transport => $ref->{transport}, can_signup => $ref->{can_signup}, default_quota => $ref->{default_quota}, default_ndquota => $ref->{default_netdiskquota}, default_expire => $ref->{default_expire}, disablesmtpd => $ref->{disablesmtpd}, disablesmtp => $ref->{disablesmtp}, disablewebmail => $ref->{disablewebmail}, disablenetdisk => $ref->{disablenetdisk}, disableimap => $ref->{disableimap}, disablepop3 => $ref->{disablepop3}, active => $ref->{active} ? 1 : 0, } } sub get_alias_info { my $self = shift; my $SQL = "SELECT * FROM alias WHERE address='$_[0]'"; my $ref = $self->get_entry($SQL); return undef unless ($ref); my $goto = $ref->{goto}; return { alias => $ref->{address}, domain => $ref->{domain}, goto => ($goto =~ m!,!) ? [split(/,/,$goto)] : $goto, active => $ref->{active} ? 1 : 0, create => $ref->{createdate}, expire => $ref->{expiredate}, } } sub get_manager_info { my $self = shift; my $SQL = "SELECT * FROM manager where username='$_[0]'"; my $ref = $self->get_entry($SQL); return undef unless ($ref); $SQL = "SELECT domain from domain_manager WHERE username='$_[0]'"; my $ds = $self->get_entries($SQL); my $arr = []; # convert to array ref foreach (@$ds) { push @$arr, $_->{domain}; } return { manager => $ref->{username}, cn => $ref->{name}, question => $ref->{question}, answer => $ref->{answer}, disablepwdchange => $ref->{disablepwdchange} ? 1 : 0, create => $ref->{createdate}, expire => $ref->{expiredate}, type => $ref->{type}, passwd => $ref->{password}, active => $ref->{active} ? 1 : 0, domain => $arr, } } #---------------------------------# # search and sort, paging handler # #---------------------------------# # method and parameters # # $self, %opt => ( # domain => $domain, # page => $page, # filter => $filter, # NULL means retreive all # filter_type => $type # mail or name(cn) is ok # ); sub user_paging { my $self = shift; my %opt = @_; my $domain = $opt{domain}; my $page = $opt{page} || 0; my $filter = $opt{filter}; my $filter_type = $opt{filter_type}; my ($has_prev, $has_next) = (1, 0); my $psize = $self->{psize}; # page size my $begin = $page*$psize; # all un-filltered result my $all = $self->get_users_list($domain) || []; # array to contain filltered result my $arr = []; delete $self->{_ext_info}; for(my $i=0; $i[$i]; if ($filter) { next unless $e->{$filter_type} =~ /$filter/i; } push @$arr, $e; } # the result array my $res = []; for(my $i=$begin;$i[$i]; last if (scalar @$res>= $psize); } if (scalar @$res == $psize && $begin + $psize < scalar @$arr) { $has_next =1; } if ($page <= 0) { $has_prev = 0 }; # XXX ext_info $self->{_ext_info} = { total => scalar @$all }; return ($res, $has_prev, $has_next); } # # $self, %opt => ( # domain => $domain, # page => $page, # filter => $filter, # NULL means all # ) sub alias_paging { my $self = shift; my %opt = @_; my $domain = $opt{domain}; my $page = $opt{page} || 0; my $filter = $opt{filter}; my ($has_prev, $has_next) = (1, 0); my $psize = $self->{psize}; # page size my $begin = $page*$psize; # all un-filltered result my $all = $self->get_aliases_list($domain) || []; # array to contain filltered result my $arr = []; delete $self->{_ext_info}; for(my $i=0; $i[$i]; if ($filter) { next unless ($e->{alias} =~ /$filter/i || $e->{goto} =~ /$filter/i); } push @$arr, $e; } my $res = []; for(my $i=$begin; $i[$i]; last if (scalar @$res>= $psize); } if (scalar @$res == $psize && $begin + $psize < scalar @$arr) { $has_next =1; } if ($page <= 0) { $has_prev = 0 }; # XXX ext_info $self->{_ext_info} = { total => scalar @$all }; return ($res, $has_prev, $has_next); } # # $self, %opt => ( # filter => $filter, # NULL means all # filter_type => $filter_type # admin or postmaster # ) # # $self->ext_info() - return extend info for counting sub manager_paging { my $self = shift; my %opt = @_; my $page = $opt{page} || 0; my $filter = $opt{filter}; my $filter_type = $opt{filter_type}; my ($has_prev, $has_next) = (1, 0); my $psize = $self->{psize}; # page size my $begin = $page*$psize; # all un-filltered result my $all = $self->get_managers_list || []; # array to contain filltered result my $arr = []; delete $self->{_ext_info}; for(my $i=0; $i[$i]; if ($filter) { next unless ($e->{manager} =~ /$filter/i || $e->{cn} =~ /$filter/i) and $e->{type} eq $filter_type; } push @$arr, $e; } my $res = []; for(my $i=$begin; $i[$i]; last if (scalar @$res>= $psize); } if (scalar @$res == $psize && $begin + $psize < scalar @$arr) { $has_next =1; } if ($page <= 0) { $has_prev = 0 }; # XXX ext_info $self->{_ext_info} = { total => scalar @$all }; return ($res, $has_prev, $has_next); } sub ext_info { my $self = shift; return $self->{_ext_info}; } sub domain_paging { die "use Ext::MgrApp::domain_paging() instead\n"; } sub DESTORY { my $self = shift; $self->{dbh}->disconnect(); undef $self; } 1;