Subversion Repositories general

Compare Revisions

No changes between revisions

Ignore whitespace Rev 1222 → Rev 1223

/hostadmiral/branches/hibernate3/backend/delete_mailbox.sh
0,0 → 1,36
#!/bin/sh
 
#
# HostAdmiral backend. Executed with ROOT privileges
#
# The script deletes specified maildir
# FIXME: do not delete - move to archive
#
# Params:
# maildir (e.g. example.com/john)
#
# Return:
# 0 - done
# 3 - wrong params
# 4 - some error
# 5 - error from system
#
 
### config ################################################
 
. `dirname $0`/scripts.conf
 
### validate params #######################################
 
if [ -z "$1" -o -n "$2" ] ; then echo "Wrong params 1"; exit 3; fi
 
DIR="/$1/"
echo "$DIR" | awk '$0 ~ "//" || $0 ~ "/\\./" || $0 ~ "/\\.\\./" || $0 !~ "^/(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)/[a-zA-Z]([a-zA-Z0-9._-]*[a-zA-Z0-9])?/$" {exit 1} {exit 0}'
if [ $? -ne 0 ]; then
echo "Wrong params 2"; exit 3
fi
 
### work ##################################################
 
rm -rf "${ROOT}${DIR}"
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/move_domain.sh
0,0 → 1,55
#!/bin/sh
 
#
# HostAdmiral backend. Executed with ROOT privileges
#
# The script moves specified old domain dir to new one
#
# Params:
# old_domain (e.g. example.com)
# new_domain (e.g. domain.net)
#
# Return:
# 0 - nothing done, all OK
# 2 - dir moved
# 3 - wrong params
# 4 - some error
# 5 - error from system
#
 
### config ################################################
 
. `dirname $0`/scripts.conf
 
### validate params #######################################
 
if [ -z "$2" -o -n "$3" ] ; then echo "Wrong params 1"; exit 3; fi
 
OLD="/$1/"
echo "$OLD" | awk '$0 ~ "//" || $0 ~ "/\\./" || $0 ~ "/\\.\\./" || $0 !~ "^/(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)/$" {exit 1} {exit 0}'
if [ $? -ne 0 ]; then
echo "Wrong params 2"; exit 3
fi
 
NEW="/$2/"
echo "$NEW" | awk '$0 ~ "//" || $0 ~ "/\\./" || $0 ~ "/\\.\\./" || $0 !~ "^/(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)/$" {exit 1} {exit 0}'
if [ $? -ne 0 ]; then
echo "Wrong params 3"; exit 3
fi
 
### work ##################################################
 
if [ -d "${ROOT}${NEW}" ] ; then
# the new maildir exists already - error
exit 4;
else
# the new maildir doesn't exist and the old one doesn't exist - do nothing
if [ ! -d "${ROOT}${OLD}" ] ; then
exit 0;
fi
 
# the new maildir doesn't exist the old one exists - move it
mv "${ROOT}${OLD}" "${ROOT}${NEW}" || { echo "Cannot move maildir"; exit 5; }
exit 2;
fi
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/move_mailbox.sh
0,0 → 1,89
#!/bin/sh
 
#
# HostAdmiral backend. Executed with ROOT privileges
#
# The script must ensure the specified new maildir exists and
# has correct structure. If old mailbox found it must be moved
# to new one. In any case the new maildir has specified owner
#
# Params:
# maildir_owner_uid (e.g. 1001)
# old_maildir (e.g. example.com/john)
# new_maildir (e.g. domain.net/merry)
#
# if old_maildir should not exist first param is equal to the second one
#
# Return:
# 0 - nothing done, all OK
# 1 - empty maildir created
# 2 - old maildir moved or owner changed
# 3 - wrong params
# 4 - some error
# 5 - error from system
#
 
### config ################################################
 
. `dirname $0`/scripts.conf
 
### validate params #######################################
 
if [ -z "$3" -o -n "$4" ] ; then echo "Wrong params 1"; exit 3; fi
 
OWNER="$1"
if [ -z "${OWNER}" ] ; then
OWNER=${DEFAULT_OWNER}
elif [ ! "${OWNER}" -ge 1000 ] ; then
echo "Wrong params 2"; exit 3
fi
 
OLD="/$2/"
echo "$OLD" | awk '$0 ~ "//" || $0 ~ "/\\./" || $0 ~ "/\\.\\./" || $0 !~ "^/(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)/[a-zA-Z]([a-zA-Z0-9._-]*[a-zA-Z0-9])?/$" {exit 1} {exit 0}'
if [ $? -ne 0 ]; then
echo "Wrong params 3"; exit 3
fi
 
NEW="/$3/"
echo "$NEW" | awk '$0 ~ "//" || $0 ~ "/\\./" || $0 ~ "/\\.\\./" || $0 !~ "^/(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)/[a-zA-Z]([a-zA-Z0-9._-]*[a-zA-Z0-9])?/$" {exit 1} {exit 0}'
if [ $? -ne 0 ]; then
echo "Wrong params 4"; exit 3
fi
 
### work ##################################################
 
if [ -d "${ROOT}${NEW}" ] ; then
# the new maildir exists and is the same as old one - do nothing
if [ "$OLD" = "$NEW" ] ; then
WRONG_OWNER=`find "${ROOT}${NEW}" -not -user "${OWNER}"`
if [ -z "$WRONG_OWNER" ] ; then
exit 0; # correct owner
else
chown -R "${OWNER}" "${ROOT}${NEW}" || { echo "Cannot change owner"; exit 5; }
exit 2;
fi
fi
 
# the new maildir exists and differs from old one - error
exit 4;
else
# the new maildir doesn't exist and is the same as old one or the old one doesn't exist
# - create an empty
if [ "$OLD" = "$NEW" -o ! -d "${ROOT}${OLD}" ] ; then
mkdir -p "${ROOT}${NEW}cur" "${ROOT}${NEW}new" "${ROOT}${NEW}tmp" \
|| { echo "Cannot create maildir"; exit 5; }
chmod 700 "${ROOT}${NEW}" || { echo "Cannot change mod"; exit 5; }
chown -R "${OWNER}" "${ROOT}${NEW}" || { echo "Cannot change owner"; exit 5; }
chown -R "${DEFAULT_OWNER}" `dirname "${ROOT}${NEW}"` || { echo "Cannot change owner"; exit 5; }
exit 1;
fi
 
# the new maildir doesn't exist, differs from old one and the old one exists
# - move it
mkdir -p `dirname "${ROOT}${NEW}"`
chown -R "${DEFAULT_OWNER}" `dirname "${ROOT}${NEW}"` || { echo "Cannot change owner"; exit 5; }
mv "${ROOT}${OLD}" "${ROOT}${NEW}" || { echo "Cannot move maildir"; exit 5; }
chown -R "${OWNER}" "${ROOT}${NEW}" || { echo "Cannot change owner"; exit 5; }
exit 2;
fi
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/scripts.conf.sample
0,0 → 1,9
#
# HostAdmiral backend. Executed with ROOT privileges
#
# configuration for *.sh scripts
#
 
ROOT=/var/spool/mail
DEFAULT_OWNER=202
 
/hostadmiral/branches/hibernate3/backend/backend.pl
0,0 → 1,1479
#!/usr/bin/perl -w
 
###################################################################################################
# #
# Sample backend for HostAdmiral #
# (copyleft) Anatoli Klassen #
# #
###################################################################################################
 
#
# Design:
#
# * Each handle has to (exactly in given order):
# 1. check input data, exit immediately if any errors;
# 2. open DB transaction;
# 3. update DB;
# 4. call external script;
# 5. set 2** status in request;
# 6. commit transaction.
#
# Simple handlers, which do not use DB may omit all steps 2, 3, 6 (but not part of them).
#
# If handler exits after step 1 - it just 'info' for admin, but should never hapen
# with error-free frontend.
#
# Handler may exit during steps 2-6, but it should hapen on wrong configured system
# (DB state problems, wrong file structure, insufficient rights etc), so it
# is 'error' for admin.
#
# Handler may no 'die' - it's program error. So it has to use blocks with possible
# 'die' (e.g. all DB operations) in 'eval' and then analizy '$@'.
#
 
# FIXME secure to show wrong (m.b. hacked) strings in logs and socket answers?
# FIXME double check validate_* functions
 
use strict;
use vars;
use subs;
use Socket;
use DBI;
use Time::HiRes qw( gettimeofday tv_interval );
use File::Basename qw( dirname );
 
#=== configuration ================================================================================
 
my $base_dir = dirname($0);
 
our $host = '127.0.0.1';
our $port;
our $password;
our $db_url;
our $db_user;
our $db_password;
our $log_level = 0; # 0 - none, 9 - all
our $sudo = '/usr/local/bin/sudo'; # path to sudo
 
my $config_name = "$base_dir/backend.conf";
require $config_name; # read the config
 
#=== constants ====================================================================================
 
# protocol description
my $protocol_ver_maj = '1';
my $protocol_ver_min = '0';
my $protocol_header = 'HostAdmiral_TcpListener';
my $password_header = 'password=';
my $domain_header = 'inetDomain';
my $user_header = 'user';
my $system_user_header = 'systemUser';
my $mailbox_header = 'mailbox';
my $mail_alias_header = 'mailAlias';
my $create_action = 'create';
my $modify_action = 'modify';
my $delete_action = 'delete';
 
# response codes
my $code_ok = 200;
my $code_ok_but = 201;
my $code_ignored = 202;
my $code_some_error = 400;
my $code_no_body = 400;
my $code_protocol_header = 401;
my $code_no_end_lines = 402;
my $code_no_password = 403;
my $code_wrong_password = 404;
my $code_no_command = 405;
my $code_wrong_command = 406;
my $code_unknown_command = 407;
my $code_wrong_params = 408;
my $code_db_connect_error = 501;
my $code_db_error = 502;
my $code_db_close_error = 503;
my $code_db_inconsistent = 504;
my $code_exec_error = 505;
my $code_script_error = 506;
my $code_panic = 600;
 
#=== internal global variables ====================================================================
 
my %handlers;
my $database_connection;
my $database_in_use = 0;
my $database_in_transaction = 0;
 
#=== functions ====================================================================================
 
sub handle_request
{
my $request = shift @_;
 
log_debug("Handle request [\n" . strip_request_password($request->{'body'}) . "]");
 
my @lines = split /\n/, $request->{'body'}, -1;
my $cur = 0;
 
# check header
if($#lines < $cur) {
set_request_code($request, $code_no_body, "Request without body");
return;
}
unless($lines[$cur] =~ /^$protocol_header $protocol_ver_maj\.\d+$/) {
set_request_code($request, $code_protocol_header, "Request must start"
. "with [$protocol_header $protocol_ver_maj.minor],"
. " but [$lines[$cur]] found");
return;
}
$cur++;
 
# check end lines
if($#lines < $cur+1 || $lines[$#lines-1] ne '' || $lines[$#lines] ne '') {
set_request_code($request, $code_no_end_lines,
"Request doesn't end with \\n\\n");
return;
}
 
# check password
if($password) {
if($#lines < $cur || !($lines[$cur] =~ /^$password_header/)) {
set_request_code($request, $code_no_password,
"Password not specified");
return;
}
 
unless($lines[$cur] =~ /^$password_header$password$/) {
set_request_code($request, $code_wrong_password,
"Wrong password");
return;
}
 
$cur++;
}
 
# get command handler
if($#lines < $cur) {
set_request_code($request, $code_no_command, "Empty command");
return;
}
unless($lines[$cur] =~ /^(\S+)\t(\S+)/) {
set_request_code($request, $code_wrong_command, "Can not get command");
return;
}
 
$request->{'command'} = $1;
$request->{'subcommand'} = $2;
$request->{'handler'} = $handlers{"$1_$2"};
 
unless($request->{'handler'}) {
set_request_code($request, $code_unknown_command,
"Unknown command [$request->{'command'} $request->{'subcommand'}]");
return;
}
 
# call
log_debug("call $request->{'command'}_$request->{'subcommand'}");
my @params = @lines[$cur..$#lines-2];
$database_in_transaction = 0;
eval {
&{$request->{'handler'}}($request, @params);
};
if($@) {
log_panic("Handler $request->{'command'}_$request->{'subcommand'} died: $@");
set_request_code($request, $code_panic, "Internal error");
}
 
# rollback transaction if it's still open
if($database_in_transaction) {
eval {
log_error("Rollback transaction");
db_rollback_transaction();
};
if($@) {
log_error("Cannot rollback transaction: $@");
}
$database_in_transaction = 0;
}
 
# last check
if(!$request->{'code'} || !$request->{'response'}) {
log_panic("Empty request code or response");
set_request_code($request, $code_panic, "Internal error");
}
}
 
### request handlers ##############################################################################
 
#-- user handlers ---------------------------------------------------------------------------------
 
sub handle_user_create
{
my $request = shift @_;
 
set_request_code($request, $code_ignored, "Not interesting in users");
}
 
sub handle_user_modify
{
my $request = shift @_;
 
set_request_code($request, $code_ignored, "Not interesting in users");
}
 
sub handle_user_delete
{
my $request = shift @_;
 
set_request_code($request, $code_ignored, "Not interesting in users");
}
 
#-- system user handlers --------------------------------------------------------------------------
 
sub handle_system_user_create
{
my $request = shift @_;
 
set_request_code($request, $code_ignored, "Not interesting in system users");
}
 
sub handle_system_user_modify
{
my $request = shift @_;
 
set_request_code($request, $code_ignored, "Not interesting in system users");
}
 
sub handle_system_user_delete
{
my $request = shift @_;
 
set_request_code($request, $code_ignored, "Not interesting in system users");
}
 
#-- domain handlers -------------------------------------------------------------------------------
 
sub save_domain
{
my $request = shift @_;
my $oldName = shift @_;
my $name = shift @_;
my $comment = shift @_;
 
my $res_action = save_to_db($request, 'transport',
{ domain => $oldName },
{ domain => $name, comment => $comment, transport => 'virtual:' } );
# FIXME: transport => 'procmail:'? then restart mail system by transport change too
return 'error' if($res_action eq 'error');
# update users and aliases tables
if($name ne $oldName) {
my $dbh = db_begin($request);
return 'error' unless($dbh);
 
eval {
$dbh->do("update users set domain=?, login=concat(name,'\@',?),"
. " maildir=concat(?,'/',name,'/'), mailid=concat(id,'\@',?)"
. " where domain=?",
undef, $name, $name, $name, $name, $oldName);
};
if($@) {
return 'error' unless(db_end($request));
set_request_code($request, $code_db_inconsistent, "Cannot update users");
return 'error';
}
 
eval {
$dbh->do("update aliases set domain=?, alias=concat(name,'\@',?) where domain=?",
undef, $name, $name, $oldName);
};
if($@) {
return 'error' unless(db_end($request));
set_request_code($request, $code_db_inconsistent, "Cannot update aliases");
return 'error';
}
 
eval {
$dbh->do("update aliases set rcpt_domain=?, rcpt=concat(rcpt_name,'\@',?)"
. " where rcpt_domain=?",
undef, $name, $name, $oldName);
};
if($@) {
return 'error' unless(db_end($request));
set_request_code($request, $code_db_inconsistent, "Cannot update aliases");
return 'error';
}
 
return 'error' unless(db_end($request));
}
 
# restart postfix
if($res_action eq 'insert' || ($res_action eq 'update' && $name ne $oldName)) {
return 'error' unless(restart_mail_system());
}
# move domain dir
if($oldName ne $name) {
my $call_res = call_external_script($request, 'move_domain.sh', [ $oldName, $name ]);
log_debug("move_domain.sh: $call_res");
if($call_res >= 3) {
set_request_code($request, $code_script_error, "Cannot move domain dir");
return 'error';
}
}
 
return $res_action;
}
 
sub handle_domain_create
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ name => \&validate_domain,
enabled => \&validate_boolean,
comment => \&validate_comment } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $res_action = save_domain($request, $params{'name'},
$params{'name'}, $params{'comment'});
return if($res_action eq 'error');
 
# step 5.
if($res_action eq 'update') {
set_request_code($request, $code_ok_but, "Domain exists, modified");
}
elsif($res_action eq 'insert') {
set_request_code($request, $code_ok, "Domain created");
}
 
# step 6.
db_commit_transaction($request);
}
 
sub handle_domain_modify
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ oldName => \&validate_domain,
name => \&validate_domain,
enabled => \&validate_boolean,
comment => \&validate_comment } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $res_action = save_domain($request, $params{'oldName'},
$params{'name'}, $params{'comment'});
return if($res_action eq 'error');
 
# step 5.
if($res_action eq 'update') {
set_request_code($request, $code_ok, "Domain modified");
}
elsif($res_action eq 'insert') {
set_request_code($request, $code_ok_but, "Domain not found, created");
}
 
# step 6.
db_commit_transaction($request);
}
 
sub handle_domain_delete
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ name => \&validate_domain } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# step 3.
my $res_action = delete_from_db($request, 'transport',
{ domain => $params{'name'} } );
 
# step 4.
if($res_action ne 'error') {
my $call_res = call_external_script($request, 'delete_domain.sh', [ $params{'name'} ]);
log_debug("delete_domain.sh: $call_res");
if($call_res >= 3) {
set_request_code($request, $code_script_error, "Cannot delete domain dir");
return;
}
}
if($res_action eq 'delete') {
return unless(restart_mail_system());
}
 
# step 5.
if($res_action eq 'delete') {
set_request_code($request, $code_ok, "Domain deleted");
}
elsif($res_action eq 'not found') {
set_request_code($request, $code_ok_but, "Domain not found");
}
 
# step 6.
db_commit_transaction($request);
}
 
#-- mailbox handlers ------------------------------------------------------------------------------
 
sub update_mailbox_mailid
{
my $request = shift @_;
my $id = shift @_;
my $login = shift @_;
my $domain = shift @_;
 
return 1 unless($id);
 
my $res_action2 = save_to_db($request, 'users',
{ login => "$login\@$domain" },
{ mailid => "$id\@$domain" });
return 0 if($res_action2 eq 'error');
 
# create alias loop
my $res_action3 = save_to_db($request, 'aliases',
{ alias => "$id\@$domain", rcpt => "$id\@$domain" },
{ name => $id,
domain => $domain,
alias => "$id\@$domain",
rcpt_name => $id,
rcpt_domain => $domain,
rcpt => "$id\@$domain",
comment => "loop for $login\@$domain" } );
return 0 if($res_action3 eq 'error');
 
return 1;
}
 
sub save_mailbox
{
my $request = shift @_;
my $oldLogin = shift @_;
my $oldDomain = shift @_;
my $login = shift @_;
my $domain = shift @_;
my $password = shift @_;
my $expired = shift @_;
my $comment = shift @_;
my $systemUser = shift @_;
# insert or update main information
my $id;
my $res_action = save_to_db($request, 'users',
{ login => "$oldLogin\@$oldDomain" },
{ name => $login,
domain => $domain,
login => "$login\@$domain",
password => $password,
maildir => "$domain/$login/",
expired => $expired,
comment => $comment,
uid => $systemUser },
\$id);
 
# set mailid for the new record
return 'error' unless(update_mailbox_mailid($request, $id, $login, $domain));
 
# update aliases table
if($domain ne $oldDomain || $login ne $oldLogin) {
my $dbh = db_begin($request);
return 'error' unless($dbh);
 
# find id
eval {
my $select_sth = $dbh->prepare("select id from users where login=?");
$select_sth->execute("$login\@$domain");
$id = $select_sth->fetchrow_array();
};
if(!$id || $@) {
return 'error' unless(db_end($request));
set_request_code($request, $code_db_inconsistent, "Cannot find id");
return;
}
 
# aliases
if($domain ne $oldDomain) {
eval {
$dbh->do("update aliases set rcpt_domain=?, rcpt=concat(rcpt_name,'\@',?)"
. " where rcpt_name=? and rcpt_domain=?",
undef, $domain, $domain, $id, $oldDomain);
};
if($@) {
return 'error' unless(db_end($request));
set_request_code($request, $code_db_inconsistent, "Cannot update aliases");
return 'error';
}
}
 
# the loop
eval {
$dbh->do("update aliases set domain=?, alias=?, rcpt_domain=?, rcpt=?, comment=?"
. " where alias=?",
undef, $domain, "$id\@$domain", $domain, "$id\@$domain",
"loop for $login\@$domain", "$id\@$oldDomain");
};
if($@) {
return 'error' unless(db_end($request));
set_request_code($request, $code_db_inconsistent, "Cannot update loop");
return 'error';
}
 
return 'error' unless(db_end($request));
}
 
# update disk
my $call_res = call_external_script($request, 'move_mailbox.sh',
[ (defined($systemUser) ? $systemUser : ''), "$oldDomain/$oldLogin", "$domain/$login" ]);
log_debug("move_mailbox.sh: $call_res");
if($call_res >= 3) {
set_request_code($request, $code_script_error, "Cannot create maildir");
return 'error';
}
 
return $res_action;
}
 
sub handle_mailbox_create
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ login => \&validate_name,
domain => \&validate_domain,
password => \&validate_password,
enabled => \&validate_boolean,
comment => \&validate_comment,
systemUser => \&validate_system_user_id,
virusCheck => \&validate_boolean,
spamCheck => \&validate_boolean } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $res_action = save_mailbox($request,
$params{'login'}, $params{'domain'},
$params{'login'}, $params{'domain'},
$params{'password'},
($params{'enabled'} eq 'true' ? 0 : 1),
$params{'comment'},
($params{'systemUser'} ? $params{'systemUser'} : undef));
 
# step 5.
if($res_action eq 'update') {
set_request_code($request, $code_ok_but, "Mailbox exists, modified");
}
elsif($res_action eq 'insert') {
set_request_code($request, $code_ok, "Mailbox created");
}
 
# step 6.
db_commit_transaction($request);
}
 
sub handle_mailbox_modify
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ oldLogin => \&validate_name,
oldDomain => \&validate_domain,
login => \&validate_name,
domain => \&validate_domain,
password => \&validate_password,
enabled => \&validate_boolean,
comment => \&validate_comment,
systemUser => \&validate_system_user_id,
virusCheck => \&validate_boolean,
spamCheck => \&validate_boolean } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $res_action = save_mailbox($request,
$params{'oldLogin'}, $params{'oldDomain'},
$params{'login'}, $params{'domain'},
$params{'password'},
($params{'enabled'} eq 'true' ? 0 : 1),
$params{'comment'},
($params{'systemUser'} ? $params{'systemUser'} : undef));
 
# step 5.
if($res_action eq 'update') {
set_request_code($request, $code_ok, "Mailbox modified");
}
elsif($res_action eq 'insert') {
set_request_code($request, $code_ok_but, "Mailbox not found, created");
}
 
# step 6.
db_commit_transaction($request);
}
 
sub handle_mailbox_delete
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ login => \&validate_name,
domain => \&validate_domain } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
 
# get mailid
my $dbh = db_begin($request);
return unless($dbh);
 
my $mail_id;
eval {
my $select_sth = $dbh->prepare("select mailid from users where login=?");
$select_sth->execute("$params{'login'}\@$params{'domain'}");
$mail_id = $select_sth->fetchrow_array();
};
return unless(db_end($request));
if(!$mail_id || $@) {
set_request_code($request, $code_db_inconsistent, "Cannot find mailid");
return;
}
 
# clear db
my $res_action = delete_from_db($request, 'users',
{ login => "$params{'login'}\@$params{'domain'}" } );
return if($res_action eq 'error');
 
my $res_action2 = delete_from_db($request, 'aliases', { rcpt => $mail_id } );
return if($res_action2 eq 'error');
 
# delete from disk - last one, because disk has no transactions
my $call_res = call_external_script($request, 'delete_mailbox.sh',
[ "$params{'domain'}/$params{'login'}" ]);
log_debug("delete_mailbox.sh: $call_res");
if($call_res >= 3) {
set_request_code($request, $code_script_error, "Cannot delete maildir");
return;
}
 
# step 5.
if($res_action eq 'delete') {
set_request_code($request, $code_ok, "Mailbox deleted");
}
elsif($res_action eq 'not found') {
set_request_code($request, $code_ok_but, "Mailbox not found");
}
 
# step 6.
db_commit_transaction($request);
}
 
#-- mail aliases handlers -------------------------------------------------------------------------
 
sub save_mail_alias_dest
{
my $request = shift @_;
my $address = shift @_;
my $domain = shift @_;
my $comment = shift @_;
my $rcpts = shift @_;
 
# try to find mailboxes with given names
my $select_sql = "select login, mailid from users where login in (";
my $param_count = 0;
foreach my $rcpt (@$rcpts) {
$select_sql .= ',' unless($param_count++ == 0);
$select_sql .= '?';
}
$select_sql .= ')';
 
my $dbh = db_begin($request);
return unless($dbh);
 
my $select_sth = $dbh->prepare($select_sql);
$param_count = 0;
foreach my $rcpt (@$rcpts) {
$select_sth->bind_param(++$param_count, $rcpt);
}
 
$select_sth->execute || return 0;
my @row;
while(@row = $select_sth->fetchrow_array()) {
my $login = $row[0];
my $mailid = $row[1];
foreach my $rcpt (@$rcpts) {
if($rcpt eq $login) {
$rcpt = $mailid;
}
}
}
return unless(db_end($request));
 
# save
foreach my $rcpt (@$rcpts) {
log_debug("save $rcpt");
my ($rcpt_name, $rcpt_domain) = ($rcpt =~ /^(.*)\@(.*)$/);
my $res_action = save_to_db($request, 'aliases',
undef,
{ name => $address,
domain => $domain,
alias => "$address\@$domain",
rcpt_name => $rcpt_name,
rcpt_domain => $rcpt_domain,
rcpt => $rcpt,
comment => $comment } );
return 0 if($res_action eq 'error');
}
 
return 1;
}
 
sub check_mail_recipients
{
my $request = shift @_;
my $rcpts = shift @_;
 
# validate recipients
foreach (@$rcpts) {
unless(validate_email($_)) {
set_request_code($request, $code_wrong_params, "Wrong email $_");
return 0;
}
}
return 1;
}
 
sub save_mail_alias
{
my $request = shift @_;
my $oldAddress = shift @_;
my $oldDomain = shift @_;
my $address = shift @_;
my $domain = shift @_;
my $comment = shift @_;
my $rcpts = shift @_;
 
# delete all from db
my $del_action = delete_from_db($request, 'aliases',
{ alias => "$oldAddress\@$oldDomain" } );
return 'error' if($del_action eq 'error');
 
# save new
return 'error' unless(save_mail_alias_dest($request, $address, $domain,
$comment, $rcpts));
 
return $del_action;
}
 
sub handle_mail_alias_create
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ address => \&validate_name,
domain => \&validate_domain,
enabled => \&validate_boolean,
comment => \&validate_comment } );
return unless(%params);
my @rcpts = parse_command_array($request, @_);
return unless(check_mail_recipients(\@rcpts));
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $del_action = save_mail_alias($request, $params{'address'}, $params{'domain'},
$params{'address'}, $params{'domain'}, $params{'comment'}, \@rcpts);
 
# step 5.
if($del_action eq 'delete') {
set_request_code($request, $code_ok_but, "Mail alias exists, modified");
}
elsif($del_action eq 'not found') {
set_request_code($request, $code_ok, "Mail alias created");
}
 
# step 6.
db_commit_transaction($request);
}
 
sub handle_mail_alias_modify
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ oldAddress => \&validate_name,
oldDomain => \&validate_domain,
address => \&validate_name,
domain => \&validate_domain,
enabled => \&validate_boolean,
comment => \&validate_comment } );
return unless(%params);
my @rcpts = parse_command_array($request, @_);
return unless(check_mail_recipients(\@rcpts));
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $del_action = save_mail_alias($request, $params{'oldAddress'}, $params{'oldDomain'},
$params{'address'}, $params{'domain'}, $params{'comment'}, \@rcpts);
 
# step 5.
if($del_action eq 'delete') {
set_request_code($request, $code_ok, "Mail alias modified");
}
elsif($del_action eq 'not found') {
set_request_code($request, $code_ok_but, "Mail alias not found, created");
}
 
# step 6.
db_commit_transaction($request);
}
 
sub handle_mail_alias_delete
{
my $request = shift @_;
# step 1.
my %params = parse_command_params($request, shift @_,
{ address => \&validate_name,
domain => \&validate_domain } );
return unless(%params);
 
# step 2.
return unless db_begin_transaction($request);
 
# steps 3. & 4.
my $res_action = delete_from_db($request, 'aliases',
{ alias => "$params{'address'}\@$params{'domain'}" } );
 
# step 5.
if($res_action eq 'delete') {
set_request_code($request, $code_ok, "Mail alias deleted");
}
elsif($res_action eq 'not found') {
set_request_code($request, $code_ok_but, "Mail alias not found");
}
 
# step 6.
db_commit_transaction($request);
}
 
### validators ####################################################################################
 
sub validate_boolean
{
$_ = shift @_;
return /^(true|false)$/ ? 1 : 0;
}
 
sub validate_comment
{
$_ = shift @_;
return /^[^\0-\10\13\14\16-\31]*$/ ? 1 : 0; # deny control chars expect \n \r and \t
}
 
sub validate_password
{
$_ = shift @_;
return /^[A-Za-z0-9.\/]{13}$/ ? 1 : 0; # crypt password
}
 
sub validate_domain
{
$_ = shift @_;
return /^(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)$/
? 1 : 0;
}
 
sub validate_name
{
$_ = shift @_;
return /^[a-zA-Z]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$/ ? 1 : 0;
}
 
sub validate_email
{
$_ = shift @_;
return /^[a-zA-Z0-9._-]+\@(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)$/
? 1 : 0; # FIXME too restrict for user name?
}
 
sub validate_param_name
{
$_ = shift @_;
return /^[a-zA-Z_][a-zA-Z0-9._-]*$/ ? 1 : 0;
}
 
sub validate_system_user_id
{
$_ = shift @_;
return 0 unless /^[0-9]*$/;
 
return ($_ eq '' || $_ >= 1000) ? 1 : 0; # additional security check
}
 
### request parsing ###############################################################################
 
sub decode_param
{
my $value = shift @_;
 
$value =~ s/\\r/\r/g;
$value =~ s/\\n/\n/g;
$value =~ s/\\t/\t/g;
$value =~ s/\\0/\000/g;
$value =~ s/\\\\/\\/g;
 
return $value;
}
 
sub parse_command_array
{
my $request = shift @_;
my @params = ();
 
map {
if(/^\t(.*)$/) {
push @params, decode_param($1);
}
} @_;
 
return @params;
}
 
sub parse_command_params($$@)
{
my $request = shift @_;
my @params = split /\t/, shift @_, -1;
my %expected = %{shift @_};
my %values = ();
 
@params = @params[2..$#params]; # remove handler and action
map {
my ($key, $value) = split /=/, $_;
 
unless(validate_param_name($key)) {
set_request_code($request, $code_wrong_params, "Wrong param name $key");
return ();
}
 
unless($expected{$key}) {
set_request_code($request, $code_wrong_params, "Param $key not expected");
return ();
}
 
my $param_value = decode_param($value);
unless(&{$expected{$key}}($param_value)) {
set_request_code($request, $code_wrong_params, "Wrong value of param $key");
return ();
}
 
$values{$key} = $param_value;
delete($expected{$key});
} @params;
 
if(%expected) {
set_request_code($request, $code_wrong_params,
"Params " . join(', ', keys %expected) . " expected but not found");
return ();
}
 
return %values;
}
 
### hanlders help functions #######################################################################
 
sub restart_mail_system
{
my $request = shift @_;
 
# FIXME do not restart to often
 
my $call_res = call_external_script($request, 'restart_mail_system.sh');
log_debug("restart_mail_system.sh: $call_res");
if($call_res >= 3) {
set_request_code($request, $code_script_error, "Cannot restart mail system");
return 0;
}
 
return 1;
}
 
sub call_external_script
{
my $request = shift @_;
my $script = shift @_;
my $params = shift @_;
 
my @args;
push @args, "$base_dir/$script";
push @args, @$params if($params);
 
my $res = system($sudo, @args);
if($res == -1) {
set_request_code($request, $code_exec_error, "Cannot execute script: $!");
return 7;
}
elsif($res & 127) {
set_request_code($request, $code_exec_error,
"Script died with signal " . ($res & 127));
return 7;
}
else {
return ($res >> 8);
}
}
 
### db functions ##################################################################################
 
sub db_connect
{
eval {
$database_connection = DBI->connect($db_url, $db_user, $db_password, {AutoCommit => 1});
$database_connection->{AutoCommit} = 0;
$database_connection->{RaiseError} = 1;
};
 
if($@) {
$database_connection = undef;
return $@;
}
 
return undef;
}
 
sub db_close
{
eval {
$database_connection->disconnect() if($database_connection);
};
}
 
sub db_reconnect
{
my $request = shift @_;
 
# connect if not yet connection
unless($database_connection) {
log_info("No DB connection, try to connect");
my $error = db_connect();
unless($database_connection) {
set_request_code($request, $code_db_connect_error, $error);
return 0;
}
}
 
# verify connection and reconnect if needed
eval {
$database_connection->selectrow_array("select 1");
};
if($@ || $database_connection->state eq 'S1000') {
log_info("Lost DB connection, try to reconnect");
my $error = db_connect();
unless($database_connection) {
set_request_code($request, $code_db_connect_error, $error);
return 0;
}
}
 
return $database_connection;
}
 
sub db_begin
{
my $request = shift @_;
 
if($database_in_use) {
log_panic("Database is already in use");
set_request_code($request, $code_panic, "Internal error");
return undef;
}
 
return undef unless(db_reconnect($request));
 
$database_in_use = 1;
return $database_connection;
}
 
sub db_end
{
my $request = shift @_;
 
unless($database_in_use) {
log_panic("Database is not in use");
set_request_code($request, $code_panic, "Internal error");
return 0;
}
 
$database_in_use = 0;
return 1;
}
 
sub db_begin_transaction
{
my $request = shift @_;
 
if($database_in_transaction) {
log_panic("Database transaction is already open");
set_request_code($request, $code_panic, "Internal error");
return 0;
}
 
return 0 unless(db_reconnect($request)),
 
$database_in_transaction = 1;
return 1;
}
 
sub db_commit_transaction
{
my $request = shift @_;
 
unless($database_in_transaction) {
log_panic("Database transaction is not open");
set_request_code($request, $code_panic, "Internal error");
return 0;
}
 
eval {
$database_connection->commit;
};
if($@) {
set_request_code($request, $code_db_error, "Cannot commit transaction");
return 0;
}
 
$database_in_transaction = 0;
return 1;
}
 
# @throws exception
sub db_rollback_transaction
{
unless($database_in_transaction) {
die("Database transaction is not open");
}
 
$database_connection->rollback;
$database_in_transaction = 0;
}
 
sub delete_from_db
{
my $request = shift @_;
my $table = shift @_;
my $key_columns = shift @_;
 
my $res_action = 'none';
my $dbh = db_begin($request);
return 'error' unless($dbh);
 
eval {
my $sql = '';
while(my ($key, $value) = each(%$key_columns)) {
next unless(defined $value);
$sql .= " and " if($sql);
$sql .= "$key = ?";
}
$sql = "delete from $table where $sql";
 
my $sth = $dbh->prepare($sql);
my $count = 0;
while(my ($key, $value) = each(%$key_columns)) {
next unless(defined $value);
$sth->bind_param(++$count, $value);
}
 
my $res = $sth->execute;
 
if($res < 1) {
$res_action = 'not found';
}
else {
$res_action = 'delete';
}
};
if($@) {
set_request_code($request, $code_db_error, "Cannot delete from DB: $@");
$res_action = 'error';
}
 
return 'error' unless(db_end($request));
 
return $res_action;
}
 
sub save_to_db
{
my $request = shift @_;
my $table = shift @_;
my $key_columns = shift @_;
my $value_columns = shift @_;
my $last_id_ref = shift @_;
 
$$last_id_ref = 0 if($last_id_ref);
 
my $error_set = 0;
my $res_action = 'none';
my $dbh = db_begin($request);
return 'error' unless($dbh);
 
eval {
my $res = 0;
 
if($key_columns) {
my $update_sql = '';
my $where = '';
while(my ($key, $value) = each(%$value_columns)) {
next unless(defined $value);
$update_sql .= ", " if($update_sql);
$update_sql .= "$key = ?";
}
while(my ($key, $value) = each(%$key_columns)) {
next unless(defined $value);
$where .= " and " if($where);
$where .= "$key = ?";
}
$update_sql = "update $table set $update_sql where $where";
 
my $update_sth = $dbh->prepare($update_sql);
my $count = 0;
while(my ($key, $value) = each(%$value_columns)) {
next unless(defined $value);
$update_sth->bind_param(++$count, $value);
}
while(my ($key, $value) = each(%$key_columns)) {
next unless(defined $value);
$update_sth->bind_param(++$count, $value);
}
 
$res = $update_sth->execute;
$res_action = 'update';
}
 
if($res < 1) {
my $insert_sql = '';
my $sql_params = '';
while(my ($key, $value) = each(%$key_columns)) {
next unless(defined $value);
next if($value_columns->{$key});
if($insert_sql) {
$insert_sql .= ", ";
$sql_params .= ", ";
}
$insert_sql .= $key;
$sql_params .= '?';
}
while(my ($key, $value) = each(%$value_columns)) {
next unless(defined $value);
if($insert_sql) {
$insert_sql .= ", ";
$sql_params .= ", ";
}
$insert_sql .= $key;
$sql_params .= '?';
}
$insert_sql = "insert into $table ($insert_sql)"
. " values ($sql_params)";
 
my $insert_sth = $dbh->prepare($insert_sql);
my $count = 0;
while(my ($key, $value) = each(%$key_columns)) {
next unless(defined $value);
next if($value_columns->{$key});
$insert_sth->bind_param(++$count, $value);
}
while(my ($key, $value) = each(%$value_columns)) {
next unless(defined $value);
$insert_sth->bind_param(++$count, $value);
}
 
$res = $insert_sth->execute;
$res_action = 'insert';
$$last_id_ref = $dbh->selectrow_array("select LAST_INSERT_ID()") if($last_id_ref);
}
};
if($@) {
set_request_code($request, $code_db_error, "Cannot update DB: $@");
$res_action = 'error';
}
 
return 'error' unless(db_end($request));
 
return $res_action;
}
 
### logging and response formers ##################################################################
 
sub strip_request_password
{
$_ = shift @_;
s/^$password_header.*$/$password_header*****/gm; # comment this line out to see password in log
return $_;
}
 
sub set_request_code
{
my $request = shift @_;
my $code = shift @_;
my $message = shift @_;
 
if($request->{'code'} || $request->{'response'}) {
log_panic("Request code or response is already set");
return;
}
 
$request->{'code'} = $code;
$request->{'response'} = $message;
 
my $error = "Error $code '$message' in request [\n"
. strip_request_password($request->{'body'}) . "]";
if($code == 600) {
log_panic($error);
}
elsif($code >= 500 && $code < 600) {
log_error($error);
}
elsif($code >= 400 && $code < 500) {
log_warning($error);
}
elsif($code >= 200 && $code < 300) {
log_info("$code $message");
}
else {
log_panic("unknown code $code");
}
}
 
sub log_debug
{
log_message('DEBUG', shift @_) if ($log_level >= 9);
}
 
sub log_info
{
log_message('INFO', shift @_) if ($log_level >= 5);
}
 
sub log_warning
{
log_message('WARN', shift @_) if ($log_level >= 3);
}
 
sub log_error
{
log_message('ERROR', shift @_) if ($log_level >= 1);
}
 
sub log_panic
{
log_message('PANIC', shift @_);
}
 
sub log_message
{
print shift @_, ":\t", shift @_, "\n";
}
 
### main functions ################################################################################
 
sub init
{
$handlers{"${user_header}_${create_action}"} = \&handle_user_create;
$handlers{"${user_header}_${modify_action}"} = \&handle_user_modify;
$handlers{"${user_header}_${delete_action}"} = \&handle_user_delete;
$handlers{"${domain_header}_${create_action}"} = \&handle_domain_create;
$handlers{"${domain_header}_${modify_action}"} = \&handle_domain_modify;
$handlers{"${domain_header}_${delete_action}"} = \&handle_domain_delete;
$handlers{"${system_user_header}_${create_action}"} = \&handle_system_user_create;
$handlers{"${system_user_header}_${modify_action}"} = \&handle_system_user_modify;
$handlers{"${system_user_header}_${delete_action}"} = \&handle_system_user_delete;
$handlers{"${mailbox_header}_${create_action}"} = \&handle_mailbox_create;
$handlers{"${mailbox_header}_${modify_action}"} = \&handle_mailbox_modify;
$handlers{"${mailbox_header}_${delete_action}"} = \&handle_mailbox_delete;
$handlers{"${mail_alias_header}_${create_action}"} = \&handle_mail_alias_create;
$handlers{"${mail_alias_header}_${modify_action}"} = \&handle_mail_alias_modify;
$handlers{"${mail_alias_header}_${delete_action}"} = \&handle_mail_alias_delete;
 
return undef;
}
 
sub connection_loop
{
# listen for connections
socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or return $!;
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1) or return $!;
bind(SERVER, sockaddr_in($port, inet_aton($host))) or return $!;
listen(SERVER, 1);
 
log_debug("Listen on $host:$port");
 
while(1) {
# get connection
my $rem_addr = accept(CLIENT, SERVER);
my $buf;
my $body = '';
my %request = ();
#log_debug("Remote: $rem_addr");
$request{'start_timestamp'} = [gettimeofday];
 
# receive request body
while((my $size = sysread CLIENT, $buf, 65536) > 0) {
$body .= $buf;
}
$request{'body'} = $body;
$request{'body_timestamp'} = [gettimeofday];
 
# call handler
handle_request(\%request);
$request{'done_timestamp'} = [gettimeofday];
 
# print out response
print CLIENT "$protocol_header $protocol_ver_maj.$protocol_ver_min"
. "\n$request{'code'} $request{'response'}\n\n";
close CLIENT;
$request{'stop_timestamp'} = [gettimeofday];
log_debug("Duration: " . tv_interval($request{'start_timestamp'},
$request{'stop_timestamp'}) . " sec");
}
 
# close the port
close SERVER;
return undef;
}
 
sub main
{
my $error;
 
$error = init();
die "Cannot init: $error\n" if($error);
 
# connect now to show any error to admin
$error = db_connect();
die "Cannot connect to DB: $error\n" if($error);
 
$error = connection_loop();
 
db_close();
die "Cannot listen for connections: $error\n" if($error);
}
 
main();
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/delete_domain.sh
0,0 → 1,37
#!/bin/sh
 
#
# HostAdmiral backend. Executed with ROOT privileges
#
# The script deletes specified domain dir in mail spool
#
# Params:
# domain (e.g. example.com)
#
# Return:
# 0 - done
# 3 - wrong params
# 4 - some error
# 5 - error from system
#
 
### config ################################################
 
. `dirname $0`/scripts.conf
 
### validate params #######################################
 
if [ -z "$1" -o -n "$2" ] ; then echo "Wrong params 1"; exit 3; fi
 
DIR="/$1/"
echo "$DIR" | awk '$0 ~ "//" || $0 ~ "/\\./" || $0 ~ "/\\.\\./" || $0 !~ "^/(([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)\\.)*([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)/$" {exit 1} {exit 0}'
if [ $? -ne 0 ]; then
echo "Wrong params 2"; exit 3
fi
 
### work ##################################################
 
if [ -d "${ROOT}${DIR}" ] ; then
rmdir "${ROOT}${DIR}" || { echo "Cannot delete domain dir"; exit 5; }
fi
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/restart_mail_system.sh
0,0 → 1,25
#!/bin/sh
 
#
# HostAdmiral backend. Executed with ROOT privileges
#
# The script forces postfix configuration reload
#
# Params:
# no
#
# Return:
# 0 - done
# 3 - wrong params
# 4 - some error
# 5 - error from system
#
 
### config ################################################
 
. `dirname $0`/scripts.conf
 
### work ##################################################
 
/usr/local/sbin/postfix reload
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/start
0,0 → 1,4
#!/bin/sh
 
sudo su -m ha-devel -c /home/www/hostadmiral.26th.net/project_devel/backend/backend.pl
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/hostadmiral/branches/hibernate3/backend/backend.conf.sample
0,0 → 1,12
# sample configuration for hostadmiral backend
 
$host = '127.0.0.1';
$port = 9090;
$password = 'password';
$db_url = 'DBI:mysql:database=mail;host=localhost;port=3306';
$db_user = 'root';
$db_password = 'mysql_password';
$log_level = 9;
 
1;
 
/hostadmiral/branches/hibernate3/backend/.
Property changes:
Added: svn:ignore
+backend.conf
+scripts.conf