maildiode 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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