odmrd
quick link: odmrd History
What is ODMR?
See RFC 2465 for the complete answer.
In short:
It nearly perfectly fits in a common setup (at least in Germany): ISP that hosts website and mail, SOHO client with a mail server and a cheap internet connection (DSL, cable or similar) without static IP address.
ODMR is "SMTP with Dynamic IP Addresses". It uses an "Authenticated TURN" (ATRN) instead of ESMTP's "Extended TURN" (ETRN) to deliver queued mail to a mailserver with a dynamic IP address (ETRN delivers only to fixed IPs).
There is usually an ODMR-client that passes the connection to the local SMTP-server. You don't have to redirect ports on your firewall, there is only a single connection from your mailserver to the ISPs odmr server.
You don't have to use POP3 to deliver mail for a whole domain to your clients, with all its downsides (e.g. losing the SMTP envelope information). Authentication is done via ESMTP's AUTH with CRAM-MD5, so your authentication information is reasonably protected against wiretapping (and possibly even against MITM-attacks).
What software supports ODMR?
Any mailserver that speaks SMTP should work with these clients:
- fetchmail is a widely used all-purpose-client to fetch mail via POP3, IMAP, ..., and (of course) ODMR. It runs on most Unizes and is delivered with possibly all Linux distributions.
- XATRN is a closed source ODMR client for Win32-platforms.
- xODMR client is a GPL ODMR client, written in Python, with a Win32-executable available
- Dave Godfrey's odmr-mailkeep was hacked together out of bits and pieces of Exim, and announced on the exim-users mailing list. It's a small and crude client with no runtime-configuration (everything hardcoded in the source).
- Some Tobit products, at least the David-Server. (They are even so convinced about ODMR they started their own high-price ODMR service called The Tobit Mail Network.)
- VPOP3 Email Server (commercial)
There are also some other odmr-servers (in no particular order). If you know about any other software, please let me know.
There are few providers that support odmr (in no particular order). If you know about more or want yours listed, let me know.
(Don't know why they are almost all in the UK...)
What is odmrd?
odmrd is a Perl script. You can either run it through (x)inetd/tcpserver or as a standalone daemon.
It was written in Perl mainly because an ODMR server seemed to be a good start to get some Perl experience.
Perl is quite easier and safer to code than C. Strings, arrays, hashes etc. are natively supported.
I planned to rewrite it in C, but it will probably not happen in the near future.
The main advantage of a C version would be performance, but that was no problem during my tests (may be a concern in very large setups, though).
Some facts about odmrd (aka features):
- Messages are read in BSMTP-format from domain-seperated directories, so its queue is separate from the MTA
- Authentication data is read from a MySQL database
- Accounting data is stored in a MySQL database
- Rejected mails are bounced, with handling of temporary and persistant errors and timeouts.
- Connection locking with detection of stale lockfiles.
So what do I need for odmrd?
odmrd is intended to run on a unix-like system (uses /proc interface). It was developed and tested on RedHat 7.x machines with Exim 3.3x, MySQL 3.23.3x, Perl 5.6.x, the current version runs on Fedora Core 1 (Perl 5.8.0).
You need
- inetd/xinetd (optional since 0.9)
- a somewhat up-to-date version of Perl with modules (see CPAN)
- Unix::Syslog
- MIME::Base64
- Digest::HMAC_MD5
- DBI and DBD::mysql
I've been told that on a Debian 6 system, you need these packages: libdbd-mysql-perl libdigest-hmac-perl libunix-syslog-perl libmime-base64-urlsafe-perl
- a MySQL server
- a mail server that is able to store single messages with SMTP envelope (BSMTP-style) in directories seperated by domain: /var/spool/odmr/$DOMAIN/$MESSAGE. I recommend Exim (with mysql support compiled in, i.e. exim4-daemon-heavy on Debian).
- odmrd
How does it work?
A mysql database is used to store the ODMR domains and the corresponding users (one user per domain, multiple domains per user). The same database is used by the MTA, so he knows which domains it should accept mail for (incoming) and the authentication information for relaying your user's mail (outgoing). The messages are stored in the file system by the MTA and picked up by odmrd when a user wants to get his mail.
How do I setup odmrd?
1. Prepare your mailserver
By default odmrd searches messages in domain-seperated files stored in /var/spool/odmr/$domain/, so you should setup your mailserver to deliver via BSMTP (batch SMTP). The files should look like this:
MAIL FROM:<some_user@example.com>
RCPT TO:<another.user@example.net>
DATA
Received: ....
From: ....
blabla
.
All lines should be terminated by CRLF.
Exim
With Exim you can do it this way:
Version 3 (old, you should upgrade to 4)
# MAIN CONFIGURATION SETTINGS
# mysql configuration, set your own parameters here
hide mysql_servers = localhost/mail/exim/secret
local_domains = [...]:mysql;SELECT domain FROM odmr.domain WHERE domain = '${quote_mysql:key}'
# TRANSPORTS CONFIGURATION
# Store mails in domain-directories for ODMR
odmr:
driver = appendfile
directory = /var/spool/odmr/$domain
bsmtp = domain
use_crlf = true
prefix =
suffix =
user = odmr
group = odmr
# DIRECTORS CONFIGURATION
odmr_domains:
driver = smartuser
transport = odmr
domains = mysql;SELECT domain FROM odmr.domain WHERE domain = '$domain'
(Note: It is possibly not really clean to consider odmr domains as local and handle them via the smartuser director. I think it would be better to use a router, but it works flawless this way.)
Version 4
### main configuration ###
# mysql configuration, set your own parameters here
hide mysql_servers = localhost/mail/exim/secret
domainlist relay_to_domains = mysql;SELECT domain FROM odmr.domain WHERE domain = '${quote_mysql:$domain}'
[...]
begin routers
odmr_router:
driver = accept
domains = mysql;SELECT domain FROM odmr.domain WHERE domain = '${quote_mysql:$domain}'
transport = odmr_transport
[...]
begin transports
odmr_transport:
driver = appendfile
directory = /var/spool/odmr/$domain
use_crlf
use_bsmtp
batch_max = 1000
user = odmr
group = odmr
If you want your Exim server to relay mail from your ODMR users, tell them to use SMTP authentication and put this into your configuration (works with Exim 3 and 4, for Exim 3 you need host_auth_accept_relay = *):
begin authenticators
# CRAM-MD5
# used by: Eudora
cram_md5:
driver = cram_md5
public_name = CRAM-MD5
server_set_id = $1
server_secret = ${lookup mysql{ \
SELECT pass FROM odmr.user \
WHERE user = '${quote_mysql:$1}'} \
{$value}fail}
# C: AUTH LOGIN [base64($user, \0, $pass)]
# - if no authentication on AUTH LOGIN:
# S: base64('Username:'), C: base64($user)
# S: base64('Password:'), C: base64($pass)
# Used by: Outlook Express
login:
driver = plaintext
public_name = LOGIN
server_prompts = "Username:: : Password::"
server_set_id = $1
server_condition = ${if eq\
{$2} \
{${lookup mysql { \
SELECT pass FROM odmr.user \
WHERE user = '${quote_mysql:$1}' \
}{$value}fail}} \
{1}{0}}
# C: AUTH PLAIN base64(\0, $user, \0, $pass)
# used by: Netscape
plain:
driver = plaintext
public_name = PLAIN
server_prompts = :
server_set_id = $2
server_condition = ${if eq \
{$3} \
{${lookup mysql { \
SELECT pass FROM odmr.user \
WHERE user = '${quote_mysql:$2}' \
}{$value}fail}} \
{1}{0}}
Postfix
Postfix does not seem to speek BSMTP natively, so you need this script and the following in your master.cf:
odmr unix - n n - - pipe
flags=h. user=odmr:odmr eol=\r\n argv=/usr/local/sbin/odmrspool.pl -f ${sender} -t ${nexthop} ${recipient}
(By courtesy of Chris Hastie, correction by Paul Lange)
2. Create a database
The intended name of the database is 'odmr' but any name will do it. For ease of use, I use the same database for odmrd and vmail-sql/tpop3d. In my newer Exim4 setup I seperated the databases and choose the right one in my queries.
CREATE DATABASE odmr;
USE odmr;
# Table structure for table 'odmr_user'
CREATE TABLE user (
user varchar(30) NOT NULL default '',
pass varchar(15) NOT NULL default '',
PRIMARY KEY (user)
) ENGINE=INNODB;
# Table structure for table 'odmr_domains'
CREATE TABLE domain (
domain varchar(64) NOT NULL default '',
user varchar(30) NOT NULL default '',
PRIMARY KEY (domain),
KEY user (user),
FOREIGN KEY (user) REFERENCES user(user)
) ENGINE=INNODB;
Populate the table user
with your designated ODMR clients (the usernames are up to you) and the table domain
with the domains you want to relay mail for and the corresponding user.
Example:
use odmr;
INSERT INTO user (user, pass) VALUES ('user1', 'pass1'), ('user2', 'pass2');
INSERT INTO domain (domain, user) VALUES ('example.net', 'user1'), ('example.org', 'user1'), ('example.com', 'user2');
This essentialy means:
- relay domains example.net and example.org to user1 (which authenticates with password pass1)
- relay domain example.com to user2 (which authenticates with password pass2)
2.1 Accounting
If you want accounting you need a third table:
CREATE TABLE acct (
user varchar(30) NOT NULL default '',
date int(10) unsigned NOT NULL default '0',
b_in int(10) unsigned NOT NULL default '0',
b_out int(10) unsigned NOT NULL default '0',
PRIMARY KEY (user,date)
) TYPE=MyISAM;
To retrieve the accounting information, I use this script:
#!/bin/sh
MONTH=${1:-$(date +%Y%m)};
mysql -t -uUSERNAME -pPASSWORD DATABASE -e "SELECT user, SUM(b_in)/1024/1024 'm in', SUM(b_out)/1024/1024 'm out', (SUM(b_in)+SUM(b_out))/1024/1024 'm total' FROM odmr_acct WHERE date BETWEEN '${MONTH}00' AND '${MONTH}99' GROUP BY user"
and this in the crontab:
1 0 1 * * acct-odmr.sh `/bin/date -d "last month" +\%Y\%m` | mail -s "odmr-acct" accounting@your-domain.org
3. Setup server
If it's not already there, put this in your /etc/services:
odmr 366/tcp # On Demand Mail Relay Protocol
3.1 inetd-mode
- If you use xinetd (as the most these days), put a file called odmr in your /etc/xinetd.d/:
service odmr
{
flags = REUSE KEEPALIVE
socket_type = stream
wait = no
user = odmr
group = odmr
# instances = 100
per_source = 2
server = /usr/local/sbin/odmrd
log_on_success += PID HOST EXIT DURATION
log_on_failure += HOST ATTEMPT
# bind = 1.2.3.4
}
- For inetd something like this in /etc/inetd.conf should work (untested):
odmr stream tcp nowait odmr.odmr /usr/local/sbin/odmrd odmrd
- With tcpserver just put this into a startup script:
tcpserver -H -l0 -R 0 odmr /usr/local/sbin/odmrd
With the '0' changed to a single IP address, if you like, and any combination of your preferred tcpserver security parameters.
Or create your own daemontools environment
3.2. Standalone mode (since 0.9)
4. Install and configure odmrd
Create a group odmr and a user odmr with rwx-rights to the odmr spool directory (/var/spool/odmr).
Copy odmrd to /usr/local/sbin. If this is a new installation, copy the sample odmrd.conf to /etc and modify it to your needs (should be self-explaining). You have to change at least mysql_pass.
5. Secure your system (optional but strongly recommended)
These are the things that come to my mind just at this moment:
odmrd needs rwx-rights to the spool-directory to create its lockfiles (maybe they will be moved to another directory in a future release) and to the subdirectories to read and delete messages.
odmrd needs SELECT-rights to odmr_user and odmr_domains and SELECT, INSERT, UPDATE rights to odmr_acct.
6. Enjoy it.
Mail me if there are any problems, you have suggestions or anything else, or simply that you are using this programm.
Miscellaneous
License and disclaimer
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
The Past: History
Version 0.5
- fixed abort when localip or peerip did not resolve to a name
- lockfiles were not correctly removed after session
- completed smtp error handling. Now mails are bounced if we get a permanent error (5xx) or another error (most presumably 4xx) and the msg is older than $max_msg_days.
Note: $max_msg_age is now in days, not in hours as it was!
- accept
AUTH LOGIN
(not really for production use, just makes testing easier for me)
- some log messages changed (mostly optical). In fact, I'm not so happy with the logging right now, so it may change again.
- added support for the RSET command
- fixed some unclean aborts when the connection was lost
- fixed an abort when the auth data was emtpy
- security fix: check the username for invalid input (the SQL-command could have been abused)
- fallback to SMTP when the remote MTA does not support ESMTP
- added ESMTP's SIZE option
- fixed an abort when the session came trough ssh (thanks to Richard van der Hoff for the patch and a configuration example)
- config file: just put your settings into /etc/odmr.conf, overriding the defaults. If you are unhappy with this, comment line 86 (require "/etc/odmrd.conf";)
2004-03-13: added some information on the website for Exim 4 and relaying after SMTP-Auth
- The config file is not a perl script any more, but a plain ascii file. To convert from your old config-file, just remove the leading $, the = in the middle of every line and the quotation marks around the values.
- Standalone daemon mode. From the system resources point of view this is much better, because you save the overhead of starting the perl executable and compiling the script for every client connection. Put "standalone 1" (and "daemon 1" if you like) into the config to use it. The server listens to Port 366 on all addresses by default, but you can change this in the config.
- Change logging to be "MTA-like" (you did not have a chance before to track which message was delivered). And we use log-levels now.
- Bounces look like a RFC1894 DSN now (many thanks to Chris Hastie)
- Custom SQL lookups (if you prefer different table names e.g.) (Chris Hastie)
- You can start odmrd now with command line parameters: -d to enable debugging, -c /path/to/config to use another config file
- Linux sendfile support (zero-copy networking!). It works only with Linux right now and you need IO::sendfile2 (IO-Sendfile from CPAN is broken and the author does not respond), which is not ready for the public right now. Mail me if you want to use this.
- code cleanup, minor bugs fixes
- internal changes: config is stored in one hash now. Taint mode and strict activated. Warnings won't end the script any more.
- fixed: AUTH CRAM-MD5 failed when hostname was too long (thanks to Wouter Paesen)
- fixed: race condition with the BSMTP writer. files starting "temp." are skipped now, so setup your BSMTP writer to create "temp." files and rename them if they are completely written. the odmrspool.pl script (see above) is now modified to do this. Exim does this by default.
- change: set max_msg_age to 0 (zero) to deactivate delivery timeouts (now the default!)
2004-08-31: The mailing list is finally set up, subscribe here.
- fixed: atomic locking
- changed: the pid inside the lockfile is not checked any more because it was not portable (worked only on Linux). This will only be a problem when the odmrd process dies, but that should normally not happen.
- added: timeout_dns to specify a timeout for dns queries, defaults to 15s. (necessary if you use xatrn, which has a timeout of 20s)
- fixed: multi-line responses from the remote MTA were not parsed correctly
- fixed: odmrd.conf, max_invalid_cmd should be max_invalid_cmds (thanks to Zoong Pham)
2005-02-11
To the postfix users: You should add "h" to to the flags of odmrspool.pl in your master.cf to make postfix lower-case the domain name. (Thanks to Paul Lange for the hint)
Anyway, odmrspool.pl should handle this, so I changed it to use lower-case domain directories.
- fixed: ugly error message when mysql server is not reachable
- added: boolean settings are now disabled with 0, false, off, disabled. All other values will turn them on (but for clarity you should use 1, true, on or enabled)
Unrelated to this release, I changed the mailing list software. The fancy web interface is gone, you can now subscribe, unsubscribe and get help by mail.
- fixed: use POSIX setsid to really drop all privileges
- added: you can now specify the path to the mysql socket (mysql_socket), or the host (mysql_host) and port (mysql_port) of the mysql server. mysql_socket overrides mysql_host. Or specify any other way through the mysql config option.
- added: SMTP NOOP and right AUTH LOGIN support, for completeness' sake
- changed: added _@!# to the set of allowed characters in the login name
- fixed: under some circumstances, more than one authentication error message was printed
- changed: rewrote, simplified and corrected big parts of the whole SMTP sending machine. Protocol errors are now catched (rename message file to bad.* for manual intervention), (hopefully) all smtp codes are now recognized and treated correctly, error codes at the end of the transaction (terminating dot) are treated differently.
- changed: valid binary option values are now: 0, off, disabled, false, no and 1, on, enabled, true, yes. Everything else will give a config warning and let odmrd use the default value
Changed odmrspool.pl so that empty sender (like used in bounce messages) are allowed.
- added: print version when invoked as "odmrd -v"
- fixed: if open() of message file failed, it was deleted nonetheless, which is very bad
- reworked standalone and superserver modes to be more consistent, improved error handling and made sendfile work with both
- changed: output config only if debug is above 1
- changed: temporary error if bounce message could not be sent
- fixed: bug in the config parser regarding multi-word statements (stopped sql_pass etc. to work)
- fixed: another bug in the config parser. using off/no/etc. hopefully works now.
- added: new config keyword max_lock_age. if a lockfile is older, it is removed and the user authentication succeeds. default is 6h.
- fixed: connection losses were not detected and handled incorrectly, so mail could get lost. Upgrading recommended!
- fixed: warnings about config keys max_invalid_auth and pidfile and warning in non-standalone mode
- fixed: setting SIGCHLD to IGNORE doesn't work on FreeBSD (leaving all client processes as zombies in the process list), so we now have our own child reaper.
- changed: limited the length of user input that will be sent to syslog
2006-06-24: some patches from Pascal Lengard and Olivier Jourdat
- a patch to use CSV (search DBD::CSV on CPAN) instead of MySQL. I did not look extensively over it, but it looks ok. The main reason this won't be in odmrd is, that I do not want introduce more backend specific code, this should have been DB-independent from the beginnging (hopefully my last lesson to use proper abstraction layers...)
- a patch for poor men's QoS(sort of).
The updated odmrspool.pl creates spool files with the spam score in the name (could be easily changed to use something else). The patched odmrd then sorts the files by name before sending them, therefore lower scored messages are sent first. And you can specifiy how many files are sent during a single connection (config option max_msgs). I like the idea, but a reread_dir_after_msgs would be better.
2008-07-05
- The mailing list was shut down a while ago, so you can not subscribe to it any more (obviously)
- Some page updates (thanks to Brian Koontz)
Any hints/wishes? Mail me
Created: 2002-07-29. Last change: 2005-12-14