5,9 → 5,34 |
# (copyleft) Anatoli Klassen |
# |
|
# FIXME use transactions |
# |
# 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 |
# FIXME by delete and rename domain - should the frontend send us an event for each mailbox |
# (alias, destination etc) in the domain or should we do it at once? |
|
use strict; |
use vars; |
31,22 → 56,23 |
our $sudo = '/usr/local/bin/sudo'; # path to sudo |
|
my $config_name = "$base_dir/backend.conf"; |
require "$config_name"; # read the config |
require $config_name; # read the config |
|
# == constants ================================= |
|
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"; |
# 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; |
67,57 → 93,18 |
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_use = 0; |
my $database_in_transaction = 0; |
|
# == functions ================================= |
|
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); |
|
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 handle_request |
{ |
my $request = shift @_; |
141,7 → 128,7 |
$cur++; |
|
# check end lines |
if($#lines < $cur+1 || $lines[$#lines-1] ne "" || $lines[$#lines] ne "") { |
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; |
187,9 → 174,36 |
# call |
log_debug("call $request->{'command'}_$request->{'subcommand'}"); |
my @params = @lines[$cur..$#lines-2]; |
&{$request->{'handler'}}($request, @params); |
$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_warn("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 ###################################### |
|
sub handle_user_create |
{ |
my $request = shift @_; |
239,18 → 253,22 |
my $name = shift @_; |
my $comment = shift @_; |
|
my $res_action = save_to_db($request, "transport", |
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 |
|
if($res_action eq 'insert' || ($res_action eq 'update' && $name ne $oldName)) { |
return "error" unless(restart_mail_system()); |
return 'error' unless(restart_mail_system()); |
} |
|
if($oldName ne $name) { |
my $call_res = call_external_script($request, 'delete_domain.sh', [ $oldName ]); |
log_debug("delete_domain.sh: $call_res"); |
if($call_res >= 3) { |
set_request_code($request, $code_script_error, "Cannot delete domain dir"); |
return 'error'; |
} |
} |
|
return $res_action; |
259,15 → 277,22 |
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 } ); |
{ name => \&validate_domain, |
enabled => \&validate_boolean, |
comment => \&validate_comment } ); |
return unless(%params); |
|
my $res_action = save_domain($request, $params{"name"}, |
$params{"name"}, $params{"comment"}); |
# 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"); |
} |
274,21 → 299,31 |
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 } ); |
{ oldName => \&validate_domain, |
name => \&validate_domain, |
enabled => \&validate_boolean, |
comment => \&validate_comment } ); |
return unless(%params); |
|
my $res_action = save_domain($request, $params{"oldName"}, |
$params{"name"}, $params{"comment"}); |
# 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"); |
} |
295,30 → 330,49 |
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 } ); |
{ name => \&validate_domain } ); |
return unless(%params); |
|
my $res_action = delete_from_db($request, "transport", |
{ domain => $params{"name"} } ); |
# 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"} ]); |
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') { |
return unless(restart_mail_system()); |
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); |
} |
|
sub update_mailbox_mailid |
330,17 → 384,17 |
|
return 1 unless($id); |
|
my $res_action2 = save_to_db($request, "users", |
{ login => "$login\@$domain" }, |
my $res_action2 = save_to_db($request, 'users', |
{ login => "$login\@$domain" }, |
{ mailid => "$id\@$domain" }); |
return 0 if($res_action2 eq "error"); |
return 0 if($res_action2 eq 'error'); |
|
# create alias loop |
my $res_action3 = save_to_db($request, "aliases", |
my $res_action3 = save_to_db($request, 'aliases', |
{ alias => "$id\@$domain", rcpt => "$id\@$domain" }, |
{ alias => "$id\@$domain", rcpt => "$id\@$domain", |
comment => "loop for $login\@$domain" } ); |
return 0 if($res_action3 eq "error"); |
return 0 if($res_action3 eq 'error'); |
|
return 1; |
} |
359,7 → 413,7 |
|
# insert or update main information |
my $id; |
my $res_action = save_to_db($request, "users", |
my $res_action = save_to_db($request, 'users', |
{ login => "$oldLogin\@$oldDomain" }, |
{ login => "$login\@$domain", |
password => $password, |
370,11 → 424,16 |
\$id); |
|
# set mailid for the new record |
return "error" unless(update_mailbox_mailid($request, $id, $login, $domain)); |
return 'error' unless(update_mailbox_mailid($request, $id, $login, $domain)); |
|
# update disk |
my $call_res = call_external_script($request, 'move_mailbox.sh', |
[ (defined($systemUser) ? $systemUser : ""), "$oldDomain/$oldLogin", "$domain/$login" ]); |
[ (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; |
} |
382,25 → 441,31 |
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 } ); |
{ 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)); |
$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"); |
} |
407,32 → 472,41 |
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 } ); |
{ 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)); |
$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"); |
} |
439,40 → 513,59 |
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 } ); |
{ login => \&validate_name, |
domain => \&validate_domain } ); |
return unless(%params); |
|
my $call_res = call_external_script($request, 'delete_mailbox.sh', |
[ "$params{'domain'}/$params{'login'}" ]); |
log_debug("delete_mailbox.sh: $call_res"); |
# step 2. |
return unless db_begin_transaction($request); |
|
# steps 3. & 4. |
|
# get mailid |
my $dbh = db_begin($request); |
return unless($dbh); |
|
my $select_sth = $dbh->prepare("select mailid from users where login=?"); |
$select_sth->execute("$params{'login'}\@$params{'domain'}"); |
my $mail_id = $select_sth->fetchrow_array(); |
db_end(); |
unless($mail_id) { |
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", |
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", { alias => $mail_id } ); |
return if($res_action2 eq "error"); |
my $res_action2 = delete_from_db($request, 'aliases', { alias => $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"); |
} |
479,6 → 572,9 |
elsif($res_action eq 'not found') { |
set_request_code($request, $code_ok_but, "Mailbox not found"); |
} |
|
# step 6. |
db_commit_transaction($request); |
} |
|
sub save_mail_alias_dest |
493,10 → 589,10 |
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 .= ',' unless($param_count++ == 0); |
$select_sql .= '?'; |
} |
$select_sql .= ")"; |
$select_sql .= ')'; |
|
my $dbh = db_begin($request); |
return unless($dbh); |
518,21 → 614,35 |
} |
} |
} |
db_end(); |
return unless(db_end($request)); |
|
# save |
foreach my $rcpt (@$rcpts) { |
log_debug("save $rcpt"); |
my $res_action = save_to_db($request, "aliases", |
my $res_action = save_to_db($request, 'aliases', |
undef, |
{ alias => "$address\@$domain", |
rcpt => $rcpt, comment => $comment } ); |
return 0 if($res_action eq "error"); |
{ alias => "$address\@$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 @_; |
543,21 → 653,13 |
my $comment = shift @_; |
my $rcpts = shift @_; |
|
# validate recipients |
foreach (@$rcpts) { |
unless(validate_email($_)) { |
set_request_code($request, $code_wrong_params, "Wrong email $_"); |
return "error"; |
} |
} |
|
# delete all from db |
my $del_action = delete_from_db($request, "aliases", |
my $del_action = delete_from_db($request, 'aliases', |
{ alias => "$oldAddress\@$oldDomain" } ); |
return "error" if($del_action eq "error"); |
return 'error' if($del_action eq 'error'); |
|
# save new |
return "error" unless(save_mail_alias_dest($request, $address, $domain, |
return 'error' unless(save_mail_alias_dest($request, $address, $domain, |
$comment, $rcpts)); |
|
return $del_action; |
566,17 → 668,24 |
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 } ); |
{ 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"); |
} |
583,24 → 692,34 |
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 } ); |
{ 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"); |
} |
607,19 → 726,28 |
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 } ); |
{ address => \&validate_name, |
domain => \&validate_domain } ); |
return unless(%params); |
|
my $res_action = delete_from_db($request, "aliases", |
# 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"); |
} |
626,8 → 754,13 |
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 @_; |
662,7 → 795,8 |
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? |
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 |
679,6 → 813,8 |
return ($_ eq '' || $_ >= 1000) ? 1 : 0; # additional security check |
} |
|
### request parsing ####################################### |
|
sub decode_param |
{ |
my $value = shift @_; |
746,20 → 882,56 |
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); |
$database_connection = DBI->connect($db_url, $db_user, $db_password, {AutoCommit => 1}); |
$database_connection->{AutoCommit} = 0; |
$database_connection->{RaiseError} = 1; |
}; |
|
if($@) { |
777,7 → 949,7 |
}; |
} |
|
sub db_begin |
sub db_reconnect |
{ |
my $request = shift @_; |
|
787,7 → 959,7 |
my $error = db_connect(); |
unless($database_connection) { |
set_request_code($request, $code_db_connect_error, $error); |
return undef; |
return 0; |
} |
} |
|
795,15 → 967,30 |
eval { |
$database_connection->selectrow_array("select 1"); |
}; |
if($database_connection->state eq "S1000") { |
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 undef; |
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; |
} |
810,9 → 997,67 |
|
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 @_; |
821,10 → 1066,10 |
|
my $res_action = 'none'; |
my $dbh = db_begin($request); |
return unless($dbh); |
return 'error' unless($dbh); |
|
eval { |
my $sql = ""; |
my $sql = ''; |
while(my ($key, $value) = each(%$key_columns)) { |
next unless(defined $value); |
$sql .= " and " if($sql); |
848,8 → 1093,13 |
$res_action = 'delete'; |
} |
}; |
if($@) { |
set_request_code($request, $code_db_error, "Cannot delete from DB: $@"); |
$res_action = 'error'; |
} |
|
db_end(); |
return 'error' unless(db_end($request)); |
|
return $res_action; |
} |
|
866,14 → 1116,14 |
my $error_set = 0; |
my $res_action = 'none'; |
my $dbh = db_begin($request); |
return unless($dbh); |
return 'error' unless($dbh); |
|
eval { |
my $res = 0; |
|
if($key_columns) { |
my $update_sql = ""; |
my $where = ""; |
my $update_sql = ''; |
my $where = ''; |
while(my ($key, $value) = each(%$value_columns)) { |
next unless(defined $value); |
$update_sql .= ", " if($update_sql); |
902,8 → 1152,8 |
} |
|
if($res < 1) { |
my $insert_sql = ""; |
my $sql_params = ""; |
my $insert_sql = ''; |
my $sql_params = ''; |
while(my ($key, $value) = each(%$key_columns)) { |
next unless(defined $value); |
next if($value_columns->{$key}); |
911,8 → 1161,8 |
$insert_sql .= ", "; |
$sql_params .= ", "; |
} |
$insert_sql .= "$key"; |
$sql_params .= "?"; |
$insert_sql .= $key; |
$sql_params .= '?'; |
} |
while(my ($key, $value) = each(%$value_columns)) { |
next unless(defined $value); |
920,8 → 1170,8 |
$insert_sql .= ", "; |
$sql_params .= ", "; |
} |
$insert_sql .= "$key"; |
$sql_params .= "?"; |
$insert_sql .= $key; |
$sql_params .= '?'; |
} |
$insert_sql = "insert into $table ($insert_sql)" |
. " values ($sql_params)"; |
943,36 → 1193,18 |
$$last_id_ref = $dbh->selectrow_array("select LAST_INSERT_ID()") if($last_id_ref); |
} |
}; |
# FIXME handle exceptions? |
if($@) { |
set_request_code($request, $code_db_error, "Cannot update DB: $@"); |
$res_action = 'error'; |
} |
|
db_end(); |
return 'error' unless(db_end($request)); |
|
return $res_action; |
} |
|
sub call_external_script |
{ |
my $request = shift @_; |
my $script = shift @_; |
my $params = shift @_; |
### logging and response formers ########################## |
|
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 undef; |
} |
elsif($res & 127) { |
set_request_code($request, $code_exec_error, "Script died with signal " . ($res & 127)); |
return undef; |
} |
else { |
return ($res >> 8); |
} |
} |
|
sub strip_request_password |
{ |
$_ = shift @_; |
986,12 → 1218,20 |
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 >= 500 && $code < 600) { |
if($code == 600) { |
log_panic($error); |
} |
elsif($code >= 500 && $code < 600) { |
log_error($error); |
} |
elsif($code >= 400 && $code < 500) { |
1001,35 → 1241,42 |
log_info("$code $message"); |
} |
else { |
log_error("unknown code $code"); |
log_panic("unknown code $code"); |
} |
} |
|
sub log_debug |
{ |
log_message("DEBUG", shift @_) if ($log_level >= 9); |
log_message('DEBUG', shift @_) if ($log_level >= 9); |
} |
|
sub log_info |
{ |
log_message("INFO", shift @_) if ($log_level >= 5); |
log_message('INFO', shift @_) if ($log_level >= 5); |
} |
|
sub log_warning |
{ |
log_message("WARN", shift @_) if ($log_level >= 3); |
log_message('WARN', shift @_) if ($log_level >= 3); |
} |
|
sub log_error |
{ |
log_message("ERROR", shift @_) if ($log_level >= 1); |
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; |
1051,6 → 1298,50 |
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; |