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