maildiode 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +85 -0
- data/bin/maildiode +142 -0
- data/doc/index.html +83 -0
- data/lib/alias.rb +47 -0
- data/lib/blacklist.rb +50 -0
- data/lib/delay.rb +48 -0
- data/lib/engine.rb +344 -0
- data/lib/greylist.rb +137 -0
- data/lib/maildir.rb +113 -0
- data/lib/server.rb +93 -0
- data/lib/settings.rb +66 -0
- data/lib/util.rb +107 -0
- data/test/test_engine.rb +239 -0
- data/test/test_suite.rb +20 -0
- metadata +96 -0
data/README
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
MailDiode README
|
2
|
+
A simple incoming mail MTA SMTP daemon
|
3
|
+
Copyright 2007-2008 Kevin B. Smith
|
4
|
+
Released under the GNU GPL v3
|
5
|
+
|
6
|
+
*** NOTE ***
|
7
|
+
This code is NOT ready for production use.
|
8
|
+
You will probably lose email if you use it on a real server!!
|
9
|
+
************
|
10
|
+
|
11
|
+
PROJECT GOALS:
|
12
|
+
1. Simplicity (Ruby, incoming only, minimal core plus plugins)
|
13
|
+
2. Trivial to install and configure (GEM, easy .conf file)
|
14
|
+
3. Security (Ruby, tainting, unit tested, lack of features)
|
15
|
+
4. Modest performance (fine for personal servers)
|
16
|
+
5. Modularity (plugins for non-core features)
|
17
|
+
|
18
|
+
FEATURES:
|
19
|
+
1. Unix/Linux Daemon
|
20
|
+
2. Implements SMTP protocol
|
21
|
+
3. Hands off messages to non-core extensions
|
22
|
+
4. Full unit test suite
|
23
|
+
|
24
|
+
NON-FEATURES:
|
25
|
+
1. No outgoing mail
|
26
|
+
2. No TLS/SSL
|
27
|
+
3. No filtering, greylisting, aliases, etc. in the core
|
28
|
+
4. Not expected to handle hundreds of users
|
29
|
+
|
30
|
+
REQUIREMENTS:
|
31
|
+
1. POSIX (only tested on Ubuntu Linux)
|
32
|
+
2. Ruby 1.8
|
33
|
+
3. Gems:
|
34
|
+
a. gurgitate-mail (for maildir)
|
35
|
+
b. kirbybase (for greylisting)
|
36
|
+
|
37
|
+
INSTALLATION AND CONFIGURATION
|
38
|
+
gem install <local maildiode gem>
|
39
|
+
|
40
|
+
<edit conf file and copy to /etc/maildiode/maildiode.conf>
|
41
|
+
|
42
|
+
Optional: symlink or copy maildiode executable to /etc/init.d/
|
43
|
+
and add to runlevels as appropriate.
|
44
|
+
|
45
|
+
To run in the foreground, for debugging:
|
46
|
+
maildiode run
|
47
|
+
|
48
|
+
To start as a daemon:
|
49
|
+
maildiode start
|
50
|
+
|
51
|
+
Check status of running daemon:
|
52
|
+
maildiode status
|
53
|
+
|
54
|
+
Stop the daemon:
|
55
|
+
maildiode stop
|
56
|
+
|
57
|
+
Check version:
|
58
|
+
maildiode -- --version
|
59
|
+
|
60
|
+
TODO
|
61
|
+
- Split into multiple gems (maildiode, -greylist)
|
62
|
+
- Drop privileges
|
63
|
+
- Add debounce filter to reject bounces for mail we didn't send
|
64
|
+
- Implement greylist exemptions (see below)
|
65
|
+
- Periodically clear out old greylist entries
|
66
|
+
- Properly handle multiple recipients where some work and some fail
|
67
|
+
(Reply OK but send bounces to the failures)
|
68
|
+
- Add a return path header?
|
69
|
+
- Create SPF filter?
|
70
|
+
- Allow integrating milters and/or other external filters
|
71
|
+
|
72
|
+
|
73
|
+
SEE ALSO
|
74
|
+
|
75
|
+
Official SMTP spec:
|
76
|
+
http://rfc.net/rfc2821.html
|
77
|
+
|
78
|
+
Great SMTP implementation notes by D. J. Bernstein:
|
79
|
+
http://cr.yp.to/smtp.html
|
80
|
+
|
81
|
+
"Spam filtering for Mail Exchangers":
|
82
|
+
http://www.tldp.org/HOWTO/Spam-Filtering-for-MX/index.html
|
83
|
+
|
84
|
+
List of IP addresses that should be exempt from greylisting:
|
85
|
+
http://cvs.puremagic.com/viewcvs/greylisting/schema/whitelist_ip.txt?view=markup
|
data/bin/maildiode
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright 2007-2008 Kevin B. Smith
|
3
|
+
# This file is part of MailDiode.
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License version 3, as
|
7
|
+
# published by the Free Software Foundation.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require 'optparse'
|
18
|
+
require 'util'
|
19
|
+
require 'settings'
|
20
|
+
require 'server'
|
21
|
+
require 'maildir'
|
22
|
+
require 'daemons'
|
23
|
+
|
24
|
+
module MailDiode
|
25
|
+
class EngineFactory
|
26
|
+
def initialize(plugins_to_use, settings)
|
27
|
+
@plugins = plugins_to_use
|
28
|
+
@settings = settings
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_engine(hostname)
|
32
|
+
|
33
|
+
engine = Engine.new(hostname)
|
34
|
+
|
35
|
+
engine.set_mail_handler(MaildirMessageHandler.new(@settings))
|
36
|
+
@plugins.each do | plugin |
|
37
|
+
engine.add_filter(plugin)
|
38
|
+
end
|
39
|
+
|
40
|
+
return engine
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create_option_parser
|
45
|
+
opts = OptionParser.new do |o|
|
46
|
+
o.on_tail("-l", "--logfile PATH_TO_LOGFILE", "Set destination logfile (default #{$log_filename})") do |name|
|
47
|
+
$log_filename = name
|
48
|
+
end
|
49
|
+
|
50
|
+
o.on_tail("-v", "--verbose", "Verbose debugging (defaults false)") do
|
51
|
+
set_log_level(Logger::DEBUG)
|
52
|
+
end
|
53
|
+
|
54
|
+
o.on_tail("-h", "--help", "Show this message") do
|
55
|
+
puts "MailDiode, a very simple incoming SMTP MTA"
|
56
|
+
puts " See #{Settings.default_file}"
|
57
|
+
puts ""
|
58
|
+
puts opts
|
59
|
+
exit(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
o.on_tail("--version", "Display version number") do
|
63
|
+
puts "$VERSION"
|
64
|
+
exit(0)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
return opts
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.load_plugins(settings)
|
73
|
+
plugins = []
|
74
|
+
settings.get_settings('plugin').each do | setting |
|
75
|
+
plugins << create_plugin(setting[0], settings)
|
76
|
+
end
|
77
|
+
return plugins
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.create_plugin(plugin_name, settings)
|
81
|
+
MailDiode::log_info("Initializing plugin: [#{plugin_name}]")
|
82
|
+
case plugin_name
|
83
|
+
when 'blacklist'
|
84
|
+
require 'blacklist'
|
85
|
+
return BlacklistFilter.new(settings)
|
86
|
+
when 'delay'
|
87
|
+
require 'delay'
|
88
|
+
return DelayFilter.new(settings)
|
89
|
+
when 'alias'
|
90
|
+
require 'alias'
|
91
|
+
return AliasFilter.new(settings)
|
92
|
+
when 'greylist'
|
93
|
+
require 'greylist'
|
94
|
+
return GreylistFilter.new(settings)
|
95
|
+
else
|
96
|
+
raise "Unrecognized plugin: #{plugin_name}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
$log_filename = '/var/log/maildiode'
|
101
|
+
|
102
|
+
begin
|
103
|
+
create_option_parser.parse(ARGV)
|
104
|
+
rescue OptionParser::InvalidOption => e
|
105
|
+
puts e
|
106
|
+
exit(1)
|
107
|
+
rescue OptionParser::AmbiguousOption => e
|
108
|
+
puts e
|
109
|
+
exit(1)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def start
|
115
|
+
$SAFE = 1
|
116
|
+
|
117
|
+
puts "***********************************************************"
|
118
|
+
puts "This application has NOT been tested much, so PLEASE do not "
|
119
|
+
puts "use it on a production server with real incoming email!!"
|
120
|
+
puts "***********************************************************"
|
121
|
+
|
122
|
+
MailDiode::log_to_file($log_filename)
|
123
|
+
MailDiode::set_log_level(Logger::INFO)
|
124
|
+
|
125
|
+
config_file = MailDiode::Settings.default_file
|
126
|
+
settings = MailDiode::Settings.new(config_file)
|
127
|
+
|
128
|
+
plugins = MailDiode::load_plugins(settings)
|
129
|
+
factory = MailDiode::EngineFactory.new(plugins, settings)
|
130
|
+
MailDiode::log_info "Starting server..."
|
131
|
+
MailDiode::log_debug "Debug logging enabled"
|
132
|
+
server = MailDiode::Server.new(factory, settings)
|
133
|
+
server.start
|
134
|
+
server.join
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
Daemons.run_proc('maildiode', {:dir_mode => :system}) do
|
140
|
+
start
|
141
|
+
end
|
142
|
+
|
data/doc/index.html
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>MailDiode - Really Simple Incoming SMTP Email Server</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<h1>MailDiode</h1>
|
7
|
+
<h2>What is it?</h2>
|
8
|
+
MailDiode is an SMTP daemon that accepts incoming email and delivers
|
9
|
+
it to maildirs.
|
10
|
+
<h2>Why another SMTP server?</h2>
|
11
|
+
<p>Because all the others I have found are either too hard to configure,
|
12
|
+
or are written in insecure languages (C).
|
13
|
+
</p>
|
14
|
+
<p>SMTP servers like sendmail, postfix, and exim serve two
|
15
|
+
<strong>completely</strong> different purposes:
|
16
|
+
</p>
|
17
|
+
<ul>
|
18
|
+
<li>Outgoing mail, from you to anyone else in the world</li>
|
19
|
+
<li>Incoming mail, to you from anyone else in the world</li>
|
20
|
+
</ul>
|
21
|
+
<p>It turns out that handling outgoing mail is <strong>really hard</strong>,
|
22
|
+
while handling incoming mail is really simple.
|
23
|
+
So MailDiode handles incoming mail and not outgoing mail.
|
24
|
+
</p>
|
25
|
+
<h2>Who might use MailDiode?</h2>
|
26
|
+
Someone who wants to run their own incoming email server, and who
|
27
|
+
can rely on some other server to handle outgoing mail.
|
28
|
+
Or perhaps someone who runs both incoming and outgoing mail servers,
|
29
|
+
but realizes that allowing each daemon to handle one specific task
|
30
|
+
is simpler and (hopefully) more secure than having one service try
|
31
|
+
to handle both.
|
32
|
+
<h2>What is the status of MailDiode?</h2>
|
33
|
+
As of 2008-12-02 it is (still) in alpha test.
|
34
|
+
That is, I am starting to use it for some of my own production email.
|
35
|
+
Mostly it works pretty well.
|
36
|
+
It cannot yet be run as a daemon.
|
37
|
+
After that feature works, I'll probably publish a gem.
|
38
|
+
<h2>When you say "simple", what do you mean?</h2>
|
39
|
+
MailDiode supports virtual domains, aliasing, and greylisting.
|
40
|
+
Here is a sample config file demonstrating all those features:
|
41
|
+
<pre>
|
42
|
+
#debug warn # error warn info debug
|
43
|
+
|
44
|
+
server hostname localhost
|
45
|
+
server ip 0.0.0.0
|
46
|
+
server port 10025
|
47
|
+
server maxconnections 20
|
48
|
+
|
49
|
+
maildir kevin /home/kevins/TestMail/main/
|
50
|
+
maildir catchall /home/kevins/TestMail/other/
|
51
|
+
|
52
|
+
plugin alias
|
53
|
+
alias kevin ^kevin@qualitycode.com$
|
54
|
+
alias other @qualitycode.com$
|
55
|
+
alias other @example.com$
|
56
|
+
|
57
|
+
plugin blacklist
|
58
|
+
blacklist to \d{6,}.*@qualitycode.com$
|
59
|
+
|
60
|
+
plugin delay
|
61
|
+
delay helo 5
|
62
|
+
delay mail 6
|
63
|
+
delay rcpt 7
|
64
|
+
|
65
|
+
plugin greylist
|
66
|
+
greylist delayminutes 5
|
67
|
+
</pre>
|
68
|
+
|
69
|
+
<p>
|
70
|
+
The first section has general information about the server.
|
71
|
+
The second section sets up two maildir destinations, each identified
|
72
|
+
by an arbitrary string. (These could be local accounts, full email
|
73
|
+
addresses, or anything else).
|
74
|
+
The alias section uses regular expressions to create multiple
|
75
|
+
aliases and "catchall accounts" that route to the destinations set up
|
76
|
+
in the earlier section.
|
77
|
+
The rest of the file is used to configure any optional plugins.
|
78
|
+
</p>
|
79
|
+
<p>
|
80
|
+
That's it. Simple.
|
81
|
+
</p>
|
82
|
+
</body>
|
83
|
+
</html>
|
data/lib/alias.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Copyright 2007-2008 Kevin B. Smith
|
2
|
+
# This file is part of MailDiode.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module MailDiode
|
17
|
+
class AliasFilter < Filter
|
18
|
+
def initialize(settings)
|
19
|
+
@aliases = []
|
20
|
+
load_settings(settings)
|
21
|
+
end
|
22
|
+
|
23
|
+
def process(filterable_data)
|
24
|
+
recipient = filterable_data.recipient
|
25
|
+
@aliases.each do | pattern_and_target |
|
26
|
+
pattern = pattern_and_target[0]
|
27
|
+
target = pattern_and_target[1]
|
28
|
+
MailDiode::log_debug "alias checking: #{pattern}"
|
29
|
+
re = Regexp.new(pattern, Regexp::IGNORECASE)
|
30
|
+
if(recipient.match(re))
|
31
|
+
MailDiode::log_info "Alias #{recipient} --> #{target}"
|
32
|
+
filterable_data.recipient = target
|
33
|
+
return
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
MailDiode::log_info "No alias for #{recipient}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_settings(settings)
|
41
|
+
settings.get_settings('alias').each do | setting |
|
42
|
+
target, pattern = setting
|
43
|
+
@aliases << [pattern, target]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/blacklist.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright 2007-2008 Kevin B. Smith
|
2
|
+
# This file is part of MailDiode.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module MailDiode
|
17
|
+
class BlacklistFilter < Filter
|
18
|
+
def initialize(settings)
|
19
|
+
@blacklisted_recipients = []
|
20
|
+
load_settings(settings)
|
21
|
+
end
|
22
|
+
|
23
|
+
def helo(helo)
|
24
|
+
end
|
25
|
+
|
26
|
+
def mail(from)
|
27
|
+
end
|
28
|
+
|
29
|
+
def rcpt(recipient)
|
30
|
+
@blacklisted_recipients.each do | pattern |
|
31
|
+
re = Regexp.new(pattern, Regexp::IGNORECASE)
|
32
|
+
if(recipient.match(re))
|
33
|
+
raise MailDiode::SMTPError.new(MailDiode::SMTPError::UNKNOWN_RECIPIENT)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_settings(settings)
|
39
|
+
settings.get_settings('blacklist').each do | setting |
|
40
|
+
key, value = setting
|
41
|
+
case key
|
42
|
+
when 'to'
|
43
|
+
@blacklisted_recipients << value
|
44
|
+
else
|
45
|
+
MailDiode::log_warning "Blacklist ignoring unknown: #{key}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/delay.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Copyright 2007-2008 Kevin B. Smith
|
2
|
+
# This file is part of MailDiode.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module MailDiode
|
17
|
+
class DelayFilter < Filter
|
18
|
+
def initialize(settings)
|
19
|
+
@delay_helo = 0
|
20
|
+
@delay_mail = 0
|
21
|
+
@delay_rcpt = 0
|
22
|
+
load_settings(settings)
|
23
|
+
end
|
24
|
+
|
25
|
+
def helo(helo)
|
26
|
+
delay('helo', @settings.delay_helo)
|
27
|
+
end
|
28
|
+
|
29
|
+
def mail(from)
|
30
|
+
delay('mail', @settings.delay_mail)
|
31
|
+
end
|
32
|
+
|
33
|
+
def rcpt(recipient)
|
34
|
+
delay('rcpt', @settings.delay_rcpt)
|
35
|
+
end
|
36
|
+
|
37
|
+
def delay(name, seconds)
|
38
|
+
MailDiode::log_debug "Delaying #{name} for #{seconds} seconds..."
|
39
|
+
sleep(seconds)
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_settings(settings)
|
43
|
+
@delay_helo = settings.get_setting('delay', 'helo').to_i
|
44
|
+
@delay_mail = settings.get_setting('delay', 'mail').to_i
|
45
|
+
@delay_rcpt = settings.get_setting('delay', 'rcpt').to_i
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|