Subversion Repositories general

Compare Revisions

No changes between revisions

Ignore whitespace Rev 1081 → Rev 1082

/hostadmiral/trunk/doc/todo.txt
1,6 → 1,13
Host Admiral TODO
========================================================================================================================
 
No unique error handle by mail alias creation.
 
Additional attributes for all objects, configurable.
HTTP Basic Auth as option to replace form-based auth - to allow integration with
non-tomcat web applications.
Transaction control for listeners.
 
+/- Save user id for all db-update operations.
21,6 → 28,8
 
+/- Cascade object deletion, confirmation page.
 
Mail aliases are not cascade deleted by deleting connected mail box.
 
Check passwords quality (make a separate project for this).
 
Show filters, search.
29,6 → 38,7
 
Allow to use existing system users: enter uid or name only, check in system for full
information.
Import users from system - separate page.
 
Allow admin to define default language for server and domain.
 
48,7 → 58,7
 
Catch-all mail alias. Only one per domain.
 
Basic scripts to push changes to the system.
+/- Basic scripts to push changes to the system.
 
If mailbox is created, create an user and a mail alias for it in one step - as option.
Afterwards they are binded and change own name or deleted together. Bind by name? In witch directions?
60,9 → 70,12
Limit deep of cascade by 'before delete'.
Add/delete passwords stores of existing DB objects if they are changed in config.
If at least one store is reversable, set password in the new stores.
Double check concurency update.
Study Hibernate behavior if it cannot save some information - seems to be not recoverable, restart needed.
Done
------------------------------------------------------------------------------------------------------------------------
/hostadmiral/trunk/src/ak/hostadmiral/listener/tcp/TcpListener.java
6,46 → 6,59
import java.io.Writer;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
import ak.hostadmiral.util.ModelException;
import ak.hostadmiral.util.ConfigInit;
import ak.hostadmiral.util.ConfigUtils;
import ak.hostadmiral.core.model.*;
 
import org.apache.log4j.Logger;
 
public class TcpListener
implements
ConfigInit,
UserCreatedListener,
UserModifiedListener,
UserDeletedListener,
InetDomainCreatedListener,
InetDomainModifiedListener,
InetDomainDeletedListener,
SystemUserCreatedListener,
SystemUserModifiedListener,
SystemUserDeletedListener,
MailboxCreatedListener,
MailboxModifiedListener,
MailboxDeletedListener,
MailAliasCreatedListener,
MailAliasModifiedListener,
MailAliasDeletedListener
implements
ConfigInit,
UserCreatedListener,
UserModifiedListener,
UserDeletedListener,
InetDomainCreatedListener,
InetDomainModifiedListener,
InetDomainDeletedListener,
SystemUserCreatedListener,
SystemUserModifiedListener,
SystemUserDeletedListener,
MailboxCreatedListener,
MailboxModifiedListener,
MailboxDeletedListener,
MailAliasCreatedListener,
MailAliasModifiedListener,
MailAliasDeletedListener
{
private static final Logger logger = Logger.getLogger(TcpListener.class);
 
private static String hostname;
private static int port;
private static String password;
protected static Object lock = new Object();
 
public static final String PROTOCOL_NAME = "HostAdmiral_TcpListener";
public static final String PROTOCOL_VERSION = "0.1";
public static final String PROTOCOL_NAME = "HostAdmiral_TcpListener";
public static final String PROTOCOL_VER_MAJ = "1";
public static final String PROTOCOL_VER_MIN = "0";
public static final String PASSWORD_DIGEST = "crypt";
 
public void init(Map params)
throws ModelException
{
setHostname(((String[])params.get("hostname"))[0]);
setPort(Integer.parseInt(((String[])params.get("port"))[0]));
ConfigUtils cfg = new ConfigUtils(params);
 
// save params
setHostname(cfg.getString("hostname", "127.0.0.1", true, false));
setPort(cfg.getInteger("port", null, false).intValue());
setPassword(cfg.getString("password", null, true, true));
 
// register listeners
UserManager.getInstance().addCreatedListener(this);
UserManager.getInstance().addModifiedListener(this);
UserManager.getInstance().addDeletedListener(this);
87,37 → 100,93
port = port_;
}
 
protected static String escape(String s)
{
// FIXME: any other problem characters? optimize it?
s = s.replaceAll("\0", "\\\\0");
s = s.replaceAll("\\\\", "\\\\\\\\");
s = s.replaceAll("\t", "\\\\t");
s = s.replaceAll("\n", "\\\\n");
s = s.replaceAll("\r", "\\\\r");
return s;
}
public static void setPassword(String password_)
{
password = password_;
}
 
protected static String escape(String s)
{
// FIXME: any other problem characters? optimize it?
s = s.replaceAll("\\\\", "\\\\\\\\");
s = s.replaceAll("\0", "\\\\0");
s = s.replaceAll("\t", "\\\\t");
s = s.replaceAll("\n", "\\\\n");
s = s.replaceAll("\r", "\\\\r");
return s;
}
 
protected static void send(String message)
throws ModelException
{
synchronized(lock) {
try {
Socket socket = new Socket(hostname, port);
Writer out = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream()));
 
if(PROTOCOL_NAME != null) {
out.write(PROTOCOL_NAME);
out.write(" ");
Socket socket = new Socket(hostname, port);
 
// send request
Writer out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
 
out.write(PROTOCOL_NAME + " " + PROTOCOL_VER_MAJ + "." + PROTOCOL_VER_MIN + "\n");
if(password != null) {
out.write("password=" + password + "\n");
}
if(PROTOCOL_VERSION != null) {
out.write(PROTOCOL_VERSION);
out.write("\n");
}
out.write(message);
out.write("\n\n");
out.close();
out.flush();
socket.shutdownOutput();
 
// get response
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String line;
boolean headerDone = false;
boolean codeDone = false;
while((line = in.readLine()) != null) {
if(!headerDone) {
if(!line.matches("^" + PROTOCOL_NAME + " " + PROTOCOL_VER_MAJ + "\\.\\d+$"))
throw new ModelException(
"Wrong response: wrong header in [" + line + "]");
 
headerDone = true;
}
else if(!codeDone) {
if(line.length() < 3)
throw new ModelException(
"Wrong response: code too short [" + line + "]");
if(line.length() >= 4 && line.charAt(3) != ' ')
throw new ModelException(
"Wrong response: cannot get code from [" + line + "]");
 
int code;
try {
code = Integer.parseInt(line.substring(0, 3));
}
catch(NumberFormatException ex) {
throw new ModelException(
"Wrong response: cannot parse code from [" + line + "]");
}
 
String response = (line.length() >= 5) ? line.substring(4) : null;
 
if(code >= 200 && code < 300) {
logger.info("Response from backend [" + line + "]");
}
else if(code >= 400 && code < 600) {
throw new ModelException(
"Error from backend [" + line + "]");
}
 
codeDone = true;
}
else {
if(!line.equals(""))
throw new ModelException(
"Wrong response: no more lines expected [" + line + "]");
}
}
 
// done
socket.close();
}
catch(Exception ex) {
logger.error("Cannot send message over TCP", ex);
127,126 → 196,127
}
}
 
//=== user ====================================================================================
//=== user ====================================================================================
 
public void userCreated(User editor, User user)
throws ModelException
{
send("user\tcreate\t" + escape(user.getLogin()) + "\t"
+ escape(/* FIXME user.getPassword() */ "") + "\t"
+ user.getEnabled() + "\t"
+ escape(user.getComment()));
send("user\tcreate\tlogin=" + escape(user.getLogin())
+ "\tpassword=" + escape(user.getPassword(editor, PASSWORD_DIGEST))
+ "\tenabled=" + user.getEnabled()
+ "\tcomment=" + escape(user.getComment()));
}
 
public void userModified(User editor, User user, User oldUser)
throws ModelException
{
send("user\tmodify\t" + escape(oldUser.getLogin()) + "\t"
+ escape(user.getLogin()) + "\t"
+ escape(/* FIXME user.getPassword() */ "") + "\t"
+ user.getEnabled() + "\t"
+ escape(user.getComment()));
send("user\tmodify\toldLogin=" + escape(oldUser.getLogin())
+ "\tlogin=" + escape(user.getLogin())
+ "\tpassword=" + escape(user.getPassword(editor, PASSWORD_DIGEST))
+ "\tenabled=" + user.getEnabled()
+ "\tcomment=" + escape(user.getComment()));
}
 
public void userDeleted(User editor, User user)
throws ModelException
{
send("user\tdelete\t" + escape(user.getLogin()));
send("user\tdelete\tlogin=" + escape(user.getLogin()));
}
 
//=== inet domain =============================================================================
//=== inet domain =============================================================================
 
public void inetDomainCreated(User editor, InetDomain domain)
throws ModelException
{
send("inetDomain\tcreate\t" + escape(domain.getName()) + "\t"
+ domain.getEnabled() + "\t"
+ escape(domain.getComment()));
send("inetDomain\tcreate\tname=" + escape(domain.getName())
+ "\tenabled=" + domain.getEnabled()
+ "\tcomment=" + escape(domain.getComment()));
}
 
public void inetDomainModified(User editor, InetDomain domain, InetDomain oldDomain)
throws ModelException
{
send("inetDomain\tmodify\t" + escape(oldDomain.getName()) + "\t"
+ escape(domain.getName()) + "\t"
+ domain.getEnabled() + "\t"
+ escape(domain.getComment()));
send("inetDomain\tmodify\toldName=" + escape(oldDomain.getName())
+ "\tname=" + escape(domain.getName())
+ "\tenabled=" + domain.getEnabled()
+ "\tcomment=" + escape(domain.getComment()));
}
 
public void inetDomainDeleted(User editor, InetDomain domain)
throws ModelException
{
send("inetDomain\tdelete\t" + escape(domain.getName()));
send("inetDomain\tdelete\tname=" + escape(domain.getName()));
}
 
//=== system user =============================================================================
//=== system user =============================================================================
 
public void systemUserCreated(User editor, SystemUser systemUser)
throws ModelException
{
send("systemUser\tcreate\t" + systemUser.getUid() + "\t"
+ escape(systemUser.getName()) + "\t"
+ systemUser.getEnabled() + "\t"
+ escape(systemUser.getComment()));
send("systemUser\tcreate\tuid=" + systemUser.getUid()
+ "\tname=" + escape(systemUser.getName())
+ "\tenabled=" + systemUser.getEnabled()
+ "\tcomment=" + escape(systemUser.getComment()));
}
 
public void systemUserModified(User editor, SystemUser systemUser, SystemUser oldSystemUser)
throws ModelException
{
send("systemUser\tmodify\t" + oldSystemUser.getUid() + "\t"
+ escape(oldSystemUser.getName()) + "\t"
+ systemUser.getUid() + "\t"
+ escape(systemUser.getName()) + "\t"
+ systemUser.getEnabled() + "\t"
+ escape(systemUser.getComment()));
send("systemUser\tmodify\toldUid=" + oldSystemUser.getUid()
+ "\toldName=" + escape(oldSystemUser.getName())
+ "\tuid=" + systemUser.getUid()
+ "\tname=" + escape(systemUser.getName())
+ "\tenabled=" + systemUser.getEnabled()
+ "\tcomment=" + escape(systemUser.getComment()));
}
 
public void systemUserDeleted(User editor, SystemUser systemUser)
throws ModelException
{
send("systemUser\tdelete\t" + systemUser.getUid() + "\t" + escape(systemUser.getName()));
send("systemUser\tdelete\tuid=" + systemUser.getUid()
+ "\tname=" + escape(systemUser.getName()));
}
 
//=== mailbox =================================================================================
//=== mailbox =================================================================================
 
public void mailboxCreated(User editor, Mailbox mailbox)
throws ModelException
{
send("mailbox\tcreate\t" + escape(mailbox.getLogin()) + "\t"
+ escape(/* FIXME user.getPassword() */ "") + "\t"
+ escape(mailbox.getDomain().getName()) + "\t"
+ mailbox.getVirusCheck() + "\t"
+ mailbox.getSpamCheck() + "\t"
+ (mailbox.getSystemUser() == null ? "" : mailbox.getSystemUser().getUid().toString())
+ "\t"
+ mailbox.getEnabled() + "\t"
+ escape(mailbox.getComment()));
send("mailbox\tcreate\tlogin=" + escape(mailbox.getLogin())
+ "\tpassword=" + escape(mailbox.getPassword(editor, PASSWORD_DIGEST))
+ "\tdomain=" + escape(mailbox.getDomain().getName())
+ "\tvirusCheck=" + mailbox.getVirusCheck()
+ "\tspamCheck=" + mailbox.getSpamCheck()
+ "\tsystemUser=" + (mailbox.getSystemUser() == null
? "" : mailbox.getSystemUser().getUid().toString())
+ "\tenabled=" + mailbox.getEnabled()
+ "\tcomment=" + escape(mailbox.getComment()));
}
 
public void mailboxModified(User editor, Mailbox mailbox, Mailbox oldMailbox)
throws ModelException
{
send("mailbox\tmodify\t" + escape(oldMailbox.getLogin()) + "\t"
+ escape(oldMailbox.getDomain().getName()) + "\t"
+ escape(mailbox.getLogin()) + "\t"
+ escape(/* FIXME user.getPassword() */ "") + "\t"
+ escape(mailbox.getDomain().getName()) + "\t"
+ mailbox.getVirusCheck() + "\t"
+ mailbox.getSpamCheck() + "\t"
+ (mailbox.getSystemUser() == null ? "" : mailbox.getSystemUser().getUid().toString())
+ "\t"
+ mailbox.getEnabled() + "\t"
+ escape(mailbox.getComment()));
send("mailbox\tmodify\toldLogin=" + escape(oldMailbox.getLogin())
+ "\toldDomain=" + escape(oldMailbox.getDomain().getName())
+ "\tlogin=" + escape(mailbox.getLogin())
+ "\tpassword=" + escape(mailbox.getPassword(editor, PASSWORD_DIGEST))
+ "\tdomain=" + escape(mailbox.getDomain().getName())
+ "\tvirusCheck=" + mailbox.getVirusCheck()
+ "\tspamCheck=" + mailbox.getSpamCheck()
+ "\tsystemUser=" + (mailbox.getSystemUser() == null
? "" : mailbox.getSystemUser().getUid().toString())
+ "\tenabled=" + mailbox.getEnabled()
+ "\tcomment=" + escape(mailbox.getComment()));
}
 
public void mailboxDeleted(User editor, Mailbox mailbox)
throws ModelException
{
send("mailbox\tdelete\t" + escape(mailbox.getLogin()) + "\t"
+ escape(mailbox.getDomain().getName()));
send("mailbox\tdelete\tlogin=" + escape(mailbox.getLogin())
+ "\tdomain=" + escape(mailbox.getDomain().getName()));
}
 
//=== mail alias ==============================================================================
//=== mail alias ==============================================================================
 
private String formMailAliasDestinations(User editor, MailAlias mailAlias)
throws ModelException
271,29 → 341,29
public void mailAliasCreated(User editor, MailAlias mailAlias)
throws ModelException
{
send(" mailAlias\tcreate\t" + escape(mailAlias.getAddress()) + "\t"
+ escape(mailAlias.getDomain().getName()) + "\t"
+ mailAlias.getEnabled() + "\t"
+ escape(mailAlias.getComment())
+ formMailAliasDestinations(editor, mailAlias));
send("mailAlias\tcreate\taddress=" + escape(mailAlias.getAddress())
+ "\tdomain=" + escape(mailAlias.getDomain().getName())
+ "\tenabled=" + mailAlias.getEnabled()
+ "\tcomment=" + escape(mailAlias.getComment())
+ formMailAliasDestinations(editor, mailAlias));
}
 
public void mailAliasModified(User editor, MailAlias mailAlias, MailAlias oldMailAlias)
throws ModelException
{
send(" mailAlias\tmodify\t" + escape(oldMailAlias.getAddress()) + "\t"
+ escape(oldMailAlias.getDomain().getName()) + "\t"
+ escape(mailAlias.getAddress()) + "\t"
+ escape(mailAlias.getDomain().getName()) + "\t"
+ mailAlias.getEnabled() + "\t"
+ escape(mailAlias.getComment())
+ formMailAliasDestinations(editor, mailAlias));
send("mailAlias\tmodify\toldAddress=" + escape(oldMailAlias.getAddress())
+ "\toldDomain=" + escape(oldMailAlias.getDomain().getName())
+ "\taddress=" + escape(mailAlias.getAddress())
+ "\tdomain=" + escape(mailAlias.getDomain().getName())
+ "\tenabled=" + mailAlias.getEnabled()
+ "\tcomment=" + escape(mailAlias.getComment())
+ formMailAliasDestinations(editor, mailAlias));
}
 
public void mailAliasDeleted(User editor, MailAlias mailAlias)
throws ModelException
{
send(" mailAlias\tdelete\t" + escape(mailAlias.getAddress())+ "\t"
+ escape(mailAlias.getDomain().getName()));
send("mailAlias\tdelete\taddress=" + escape(mailAlias.getAddress())
+ "\tdomain=" + escape(mailAlias.getDomain().getName()));
}
}
/hostadmiral/trunk/src/ak/hostadmiral/core/model/PasswordStore.java
13,6 → 13,11
public String getDigest();
 
/**
* returns true if the store is able to return the original password
*/
public boolean getReversable();
 
/**
* to store to persistent store
*/
public String getPassword();
30,4 → 35,10
 
public boolean checkPassword(String password)
throws ModelException;
 
/**
* return the password in plain text if possible
*/
public String getOriginalPassword()
throws UnsupportedOperationException;
}
/hostadmiral/trunk/src/ak/hostadmiral/core/model/Mailbox.java
90,6 → 90,26
passwords.add(ps);
}
 
public String getPassword(User editor, String digest)
throws ModelException
{
if(!editableBy(editor))
throw new ModelSecurityException();
 
for(Iterator i = passwords.iterator(); i.hasNext(); ) {
Object o = i.next();
if(!(o instanceof PasswordStore))
throw new ModelException("It's not a password store");
 
PasswordStore ps = (PasswordStore)o;
if(ps.getDigest().equals(digest)) {
return ps.getPassword();
}
}
 
throw new ModelException("Digest " + digest + " not found");
}
 
public void setPassword(User editor, String password)
throws ModelException
{
/hostadmiral/trunk/src/ak/hostadmiral/core/model/PasswordStoreAbstract.java
112,4 → 112,15
theClone.password = password;
return theClone;
}
 
public boolean getReversable()
{
return false;
}
 
public String getOriginalPassword()
throws UnsupportedOperationException
{
throw new UnsupportedOperationException("Not able to restore original password");
}
}
/hostadmiral/trunk/src/ak/hostadmiral/core/model/User.java
124,6 → 124,26
return getDefaultPasswordStore().getPassword();
}
 
public String getPassword(User editor, String digest)
throws ModelException
{
if(!partEditableBy(editor))
throw new ModelSecurityException();
 
for(Iterator i = passwords.iterator(); i.hasNext(); ) {
Object o = i.next();
if(!(o instanceof PasswordStore))
throw new ModelException("It's not a password store");
 
PasswordStore ps = (PasswordStore)o;
if(ps.getDigest().equals(digest)) {
return ps.getPassword();
}
}
 
throw new ModelException("Digest " + digest + " not found");
}
 
public void setPassword(User editor, String password)
throws ModelException
{
/hostadmiral/trunk/src/ak/hostadmiral/core/model/PasswordStorePlain.java
20,4 → 20,15
{
return password;
}
 
public boolean getReversable()
{
return true;
}
 
public String getOriginalPassword()
throws UnsupportedOperationException
{
return password;
}
}
/hostadmiral/trunk/src/ak/hostadmiral/core/config/Configurator.java
112,8 → 112,18
params.put(p.getName(), (String[])p.getValues().toArray(new String[0]));
}
 
o.init(params);
try {
o.init(params);
}
catch(ModelException ex) {
logger.error("Cannot initialize instance of class "
+ c.getClass().getName(), ex);
}
}
else {
logger.error("Class " + c.getClass().getName()
+ " does not implement ak.hostadmiral.util.ConfigInit interface");
}
}
}
 
/hostadmiral/trunk/src/ak/hostadmiral/util/ConfigUtils.java
0,0 → 1,81
package ak.hostadmiral.util;
 
import java.util.Map;
 
public class ConfigUtils
{
private Map params;
protected boolean returnDefValue;
 
public ConfigUtils(Map params)
{
this.params = params;
}
 
public String getString(String key, String defValue,
boolean optional, boolean mayBeEmpty)
throws ModelException
{
String s = (String)getObject(key, defValue, optional);
 
if(returnDefValue) return s;
 
if(!mayBeEmpty && s.equals("")) {
throw new ModelException("Configuration parameter '" + key + "' may not be empty");
}
 
return s;
}
 
public Integer getInteger(String key, Integer defValue,
boolean optional)
throws ModelException
{
Object value = getObject(key, defValue, optional);
 
if(returnDefValue) return defValue;
 
return new Integer((String)value);
}
 
protected synchronized Object getObject(String key, Object defValue, boolean optional)
throws ModelException
{
returnDefValue = false;
 
Object obj = params.get(key);
 
if(obj == null) {
if(optional) {
returnDefValue = true;
return defValue;
}
else
throw new ModelException("Configuration parameter '" + key + "' must be present");
}
 
if(!(obj instanceof String[])) {
throw new ModelException("Configuration parameter '" + key
+ "' expected to be an array of strings");
}
 
String[] sa = (String[])obj;
 
if(sa.length == 0) {
if(optional) {
returnDefValue = true;
return defValue;
}
else
throw new ModelException("Value of configuration parameter '"
+ key + "' must be present");
}
 
if(sa.length > 1) {
throw new ModelException("Value of configuration parameter '"
+ key + "' may not be an array");
}
 
return sa[0];
}
}
/hostadmiral/trunk/src/ak/hostadmiral/util/ConfigInit.java
6,6 → 6,7
{
/**
* This method is called by initialization from config file.
* FIMXE give more powerful structure, not just a map
*
* @param params map String -> String[] with pairs of param name -> values
* from the initializaion file
/hostadmiral/trunk/backend/backend.pl
0,0 → 1,731
#!/usr/bin/perl -w
 
#
# Sample backend for HostAdmiral
# (copyleft) Anatoli Klassen
#
 
# FIXME use transactions
 
use strict;
use vars;
use subs;
use Socket;
use DBI;
use Time::HiRes qw( gettimeofday tv_interval );
 
# == configuration =============================
 
my $host = '127.0.0.1';
my $port = 9097;
my $password = '0123456789ABCDEF';
my $db_url = 'DBI:mysql:database=mail;host=localhost;port=3306';
my $db_user = 'root';
my $db_password = '';
my $log_level = 9; # 0 - none, 9 - all
 
# == 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";
 
# response codes
my $code_ok = 200;
my $code_ok_but = 201;
my $code_ignored = 202;
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;
 
# == internal global variables =================
 
my %handlers;
 
sub connection_loop
{
# listen for connections
socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or die "$!\n";
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1) or die "$!\n";
bind(SERVER, sockaddr_in($port, inet_aton($host))) or die "$!\n";
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;
}
 
sub handle_request
{
my $request = shift @_;
 
log_debug("Handle request [\n$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];
&{$request->{'handler'}}($request, @params);
}
 
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");
}
 
sub handle_domain_create
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("name"));
return unless(%params);
 
my $res_action = save_to_db($request, "transport",
{ domain => $params{"name"} },
{ domain => $params{"name"}, comment => $params{"comment"},
transport => 'virtual:' } );
 
if($res_action eq 'update') {
return unless(restart_mail_system());
set_request_code($request, $code_ok_but, "Domain exists, modified");
}
elsif($res_action eq 'insert') {
return unless(restart_mail_system());
set_request_code($request, $code_ok, "Domain created");
}
}
 
sub handle_domain_modify
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("oldName", "name"));
return unless(%params);
 
my $res_action = save_to_db($request, "transport",
{ domain => $params{"oldName"} },
{ domain => $params{"name"}, comment => $params{"comment"},
transport => 'virtual:' } );
 
if($res_action eq 'update') {
return unless(restart_mail_system());
set_request_code($request, $code_ok, "Domain modified");
}
elsif($res_action eq 'insert') {
return unless(restart_mail_system());
set_request_code($request, $code_ok_but, "Domain not found, created");
}
}
 
sub handle_domain_delete
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("name"));
return unless(%params);
 
my $res_action = delete_from_db($request, "transport",
{ domain => $params{"name"} } );
 
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");
}
}
 
sub handle_system_user_create
{
}
 
sub handle_system_user_modify
{
}
 
sub handle_system_user_delete
{
}
 
sub handle_mailbox_create
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_,
("login", "password", "domain"));
return unless(%params);
 
my $res_action = save_to_db($request, "users",
{ login => "$params{'login'}\@$params{'domain'}" },
{ login => "$params{'login'}\@$params{'domain'}",
password => $params{"password"},
maildir => "$params{'domain'}/$params{'login'}/",
expired => ($params{"enabled"} eq "true" ? 0 : 1),
comment => $params{"comment"},
uid => ($params{"systemUser"} ? $params{"systemUser"} : undef) } );
 
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");
}
}
 
sub handle_mailbox_modify
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_,
("oldLogin", "oldDomain", "login", "domain"));
return unless(%params);
 
# FIXME move the old maildir
 
my $res_action = save_to_db($request, "users",
{ login => "$params{'oldLogin'}\@$params{'oldDomain'}" },
{ login => "$params{'login'}\@$params{'domain'}",
password => $params{"password"},
maildir => "$params{'domain'}/$params{'login'}/",
expired => ($params{"enabled"} eq "true" ? "0" : "1"),
comment => $params{"comment"},
uid => ($params{"systemUser"} ? $params{"systemUser"} : undef) } );
 
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");
}
}
 
sub handle_mailbox_delete
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("login", "domain"));
return unless(%params);
 
# FIXME remove the maildir
 
my $res_action = delete_from_db($request, "users",
{ login => "$params{'login'}\@$params{'domain'}" } );
 
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");
}
}
 
sub handle_mail_alias_create
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("address", "domain"));
return unless(%params);
my @rcpts = parse_command_array($request, @_);
 
my $del_action = delete_from_db($request, "aliases",
{ alias => "$params{'address'}\@$params{'domain'}" } );
return if($del_action eq "error");
foreach my $rcpt (@rcpts) {
log_debug("save $rcpt");
my $res_action = save_to_db($request, "aliases",
undef,
{ alias => "$params{'address'}\@$params{'domain'}",
rcpt => $rcpt, comment => $params{"comment"} } );
return if($res_action eq "error");
}
 
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");
}
}
 
sub handle_mail_alias_modify
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("address", "domain"));
return unless(%params);
my @rcpts = parse_command_array($request, @_);
 
my $del_action = delete_from_db($request, "aliases",
{ alias => "$params{'address'}\@$params{'domain'}" } );
return if($del_action eq "error");
foreach my $rcpt (@rcpts) {
log_debug("save $rcpt");
my $res_action = save_to_db($request, "aliases",
undef,
{ alias => "$params{'address'}\@$params{'domain'}",
rcpt => $rcpt, comment => $params{"comment"} } );
return if($res_action eq "error");
}
 
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");
}
}
 
sub handle_mail_alias_delete
{
my $request = shift @_;
my %params = parse_command_params($request, shift @_, ("address", "domain"));
return unless(%params);
 
my $res_action = delete_from_db($request, "aliases",
{ alias => "$params{'address'}\@$params{'domain'}" } );
 
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");
}
}
 
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 %required = map { $_ => 1 } @_; # convert array to hash
my %values = ();
@params = @params[2..$#params]; # remove handler and action
map {
my ($key, $value) = split /=/, $_;
$values{$key} = decode_param($value);
delete($required{$key});
} @params;
 
if(%required) {
set_request_code($request, $code_wrong_params,
"Params " . join(', ', keys %required) . " expected but not found");
return ();
}
 
return %values;
}
 
sub restart_mail_system
{
my $request = shift @_;
 
log_debug("Mail system restarted");
 
return 1;
}
 
sub db_connect
{
my $request = shift @_;
my $dbh = undef;
eval { $dbh = DBI->connect($db_url, $db_user, $db_password); };
if($@) {
set_request_code($request, $code_db_connect_error, $@);
$dbh = undef;
}
 
return $dbh;
}
 
sub db_close
{
my $request = shift @_;
my $dbh = shift @_;
my $error = shift @_;
my $no_error = 1;
 
if($error) {
set_request_code($request, $code_db_error, $error);
$no_error = 0;
}
 
eval {
$dbh->disconnect() if($dbh);
};
 
if($@ && $no_error) {
set_request_code($request, $code_db_close_error, $@);
$no_error = 0;
}
 
return $no_error;
}
 
sub delete_from_db
{
my $request = shift @_;
my $table = shift @_;
my $key_columns = shift @_;
 
my $res_action = 'none';
my $dbh = db_connect($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(db_close($request, $dbh, $@)) {
return $res_action;
}
else {
return 'error';
}
}
 
sub save_to_db
{
my $request = shift @_;
my $table = shift @_;
my $key_columns = shift @_;
my $value_columns = shift @_;
 
my $error_set = 0;
my $res_action = 'none';
my $dbh = db_connect($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';
}
};
 
if(db_close($request, $dbh, $@)) {
return $res_action;
}
else {
return 'error';
}
}
 
sub set_request_code
{
my $request = shift @_;
my $code = shift @_;
my $message = shift @_;
 
$request->{'code'} = $code;
$request->{'response'} = $message;
 
my $error = "Error $code '$message' in request [\n$request->{'body'}]";
if($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_error("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_message
{
print shift @_, ":\t", shift @_, "\n";
}
 
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;
}
 
sub main
{
#my $sth = $dbh->prepare("SELECT * FROM transport");
#$sth->execute();
#while(my $ref = $sth->fetchrow_hashref()) {
# print "id = $ref->{'id'}\n";
#}
#$sth->finish();
 
init();
connection_loop();
}
 
main();
 
Property changes:
Added: svn:executable
+*
\ No newline at end of property