/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 |