maildiode 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/settings.rb ADDED
@@ -0,0 +1,66 @@
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
+
17
+ module MailDiode
18
+
19
+ class Settings
20
+ def initialize(config_file)
21
+ @settings = {}
22
+ File.foreach(config_file) do | line |
23
+ line = line.gsub(/#.*/, '').strip
24
+ if !line.empty?
25
+ component, keyword, args = line.split(' ', 3)
26
+ load_setting(component.downcase, keyword.downcase, args)
27
+ end
28
+ end
29
+ end
30
+
31
+ def load_setting(component, keyword, args)
32
+ component_settings = get_settings(component)
33
+ component_settings[keyword] = args
34
+ end
35
+
36
+ def get_settings(component)
37
+ component_settings = @settings[component]
38
+ if(!component_settings)
39
+ component_settings = {}
40
+ @settings[component] = component_settings
41
+ end
42
+ return component_settings
43
+ end
44
+
45
+ def get_setting(component, keyword)
46
+ component_settings = get_settings(component)
47
+ if(!component_settings)
48
+ return nil
49
+ end
50
+ return component_settings[keyword]
51
+ end
52
+
53
+ def get_int(component, keyword, default_value)
54
+ value = get_setting(component, keyword)
55
+ if(!value)
56
+ return default_value
57
+ end
58
+ return value.to_i
59
+ end
60
+
61
+ def Settings.default_file
62
+ '/etc/maildiode/maildiode.conf'
63
+ end
64
+ end
65
+
66
+ end
data/lib/util.rb ADDED
@@ -0,0 +1,107 @@
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
+
17
+ require 'logger'
18
+
19
+ module MailDiode
20
+ NEWLINE = "\r\n"
21
+ end
22
+
23
+
24
+ module MailDiode
25
+ class UTCFormatter < Logger::Formatter
26
+ def initialize
27
+ @datetime_format = '%Y-%m-%d %H:%M:%SZ'
28
+ end
29
+
30
+ def call(severity, time, progname, msg)
31
+ time.utc
32
+ super(severity, time, progname, msg)
33
+ end
34
+ end
35
+
36
+ def self.log_to_string
37
+ @log_buffer = StringIO.new('', 'w')
38
+ log_to_stream(@log_buffer)
39
+ end
40
+
41
+ def self.log_to_console
42
+ @log_buffer = nil
43
+ log_to_stream(STDERR)
44
+ end
45
+
46
+ def self.log_to_file(file_path)
47
+ is_safe = !file_path.index('..')
48
+ if !is_safe
49
+ raise "Log file path cannot contain .."
50
+ end
51
+ file_path.untaint
52
+ @log_buffer = nil
53
+ out = File.open(file_path, 'a')
54
+ log_to_stream(out)
55
+ end
56
+
57
+ def self.log_to_stream(destination)
58
+ level = @log ? @log.level : Logger::WARN
59
+ @log_stream = destination
60
+ @log = Logger.new(destination, 'monthly')
61
+ @log.formatter = UTCFormatter.new
62
+ @log.level = level
63
+ end
64
+
65
+ def self.get_log
66
+ return @log_buffer.string
67
+ end
68
+
69
+ def self.clear_log
70
+ @log_buffer.truncate(0)
71
+ end
72
+
73
+ def self.flush_log
74
+ @log_stream.flush
75
+ end
76
+
77
+ def self.log_error(message)
78
+ @log.error(message)
79
+ flush_log
80
+ end
81
+
82
+ def self.log_warning(message)
83
+ @log.warn(message)
84
+ flush_log
85
+ end
86
+
87
+ def self.log_info(message)
88
+ @log.info(message)
89
+ flush_log
90
+ end
91
+
92
+ def self.log_debug(message)
93
+ @log.debug(message)
94
+ flush_log
95
+ end
96
+
97
+ def self.log_success(command, args, result)
98
+ log_info("#{result}: #{command} #{args}")
99
+ end
100
+
101
+ def self.set_log_level(level)
102
+ @log.level = level
103
+ end
104
+
105
+ log_to_console
106
+
107
+ end
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright 2007-2008 Kevin B. Smith
4
+ # This file is part of MailDiode.
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3, as
8
+ # published by the Free Software Foundation.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'test/unit'
19
+ require 'stringio'
20
+
21
+ require 'util'
22
+ require 'engine'
23
+
24
+ FAKE_IP = '10.10.10.10'
25
+
26
+ class DummyFilter < MailDiode::Filter
27
+ attr_accessor :reject
28
+ attr_reader :data
29
+
30
+ def process(filterable_data)
31
+ if(reject)
32
+ raise MailDiode::SMTPError.new(reject)
33
+ end
34
+ @data = filterable_data
35
+ end
36
+ end
37
+
38
+ class DummyMailHandler < MailDiode::MailHandler
39
+ attr_reader :message_text
40
+ attr_accessor :reject
41
+
42
+ SAMPLE_ID = '12345'
43
+
44
+ def initialize
45
+ @to = []
46
+ valid = true
47
+ end
48
+
49
+ def valid_recipient?(to_address)
50
+ return !reject
51
+ end
52
+
53
+ def process_message(recipient, text_lines)
54
+ @message_text = text_lines
55
+ return SAMPLE_ID
56
+ end
57
+
58
+ def message_without_first_line
59
+ return message_text.split("\r\n")[1..-1].join("\r\n")
60
+ end
61
+ end
62
+
63
+ class TestEngine < Test::Unit::TestCase
64
+ def setup
65
+ MailDiode::set_log_level(Logger::WARN)
66
+ MailDiode::log_to_console
67
+
68
+ @engine = MailDiode::Engine.new('woohoo')
69
+ @handler = DummyMailHandler.new
70
+ @engine.set_mail_handler(@handler)
71
+
72
+ @greeting = @engine.start(FAKE_IP)
73
+ end
74
+
75
+ def teardown
76
+ end
77
+
78
+ def test_greeting
79
+ assert_equal(0, @greeting.index('220 '), "Missing 220?")
80
+ assert_match('woohoo', @greeting, "missing hostname?")
81
+ end
82
+
83
+ def test_unknown_commands
84
+ assert_equal(0, @engine.process_line('').index(MailDiode::SMTPError::BAD_COMMAND), "Blank cmd accepted?")
85
+ assert_equal(0, @engine.process_line('xxx').index(MailDiode::SMTPError::BAD_COMMAND), "Bogus cmd accepted?")
86
+ end
87
+
88
+ def test_noop
89
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('noop'), "noop failed?")
90
+ assert_equal(MailDiode::SMTPError::SYNTAX_NOOP, @engine.process_line('noop x'), "noop allowed a parameter?")
91
+ end
92
+
93
+ def test_quit
94
+ assert(!@engine.terminate?, 'terminating before QUIT?')
95
+ assert_equal(MailDiode::RESULT_BYE, @engine.process_line('quit'), "didn't say bye?")
96
+ assert(@engine.terminate?, 'not terminating after QUIT?')
97
+ assert_equal(MailDiode::SMTPError::SYNTAX_QUIT, @engine.process_line('quit x'), "quit allowed a parameter?")
98
+ end
99
+
100
+ def test_helo
101
+ assert_equal(MailDiode::SMTPError::SYNTAX_HELO, @engine.process_line('helo'), "helo without arg worked?")
102
+ assert_equal(MailDiode::SMTPError::SYNTAX_HELO, @engine.process_line('ehlo'), "ehlo without arg worked?")
103
+
104
+ helo_result = @engine.process_line('helo localhost')
105
+ assert_equal(0, helo_result.index('250'), "helo failed?")
106
+ assert_equal(helo_result, @engine.process_line('helo localhost'), "second helo failed?")
107
+ assert_equal(helo_result, @engine.process_line('ehlo localhost'), "ehlo failed?")
108
+ assert_equal(helo_result, @engine.process_line('ehlo [1.2.3.4]'), "ehlo failed?")
109
+ assert_equal(helo_result, @engine.process_line('ehlo winnie.the.poo'), "ehlo failed?")
110
+ end
111
+
112
+ def test_rset
113
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('rset'), "rset failed?")
114
+ assert_equal(MailDiode::SMTPError::SYNTAX_RSET, @engine.process_line('rset x'), "rset allowed a parameter?")
115
+
116
+ @engine.process_line('mail From:<sender@example.com>')
117
+ @engine.process_line('rset')
118
+ assert_equal(MailDiode::SMTPError::NEED_MAIL_BEFORE_RCPT, @engine.process_line('rcpt To:<recipient@example.com>'), "rset didn't clear from?")
119
+ end
120
+
121
+ def test_vrfy
122
+ assert_equal(MailDiode::SMTPError::SYNTAX_VRFY, @engine.process_line('vrfy'), "vrfy without arg worked?")
123
+
124
+ assert_equal(MailDiode::RESULT_UNSURE, @engine.process_line('vrfy anything'), "vrfy failed?")
125
+ end
126
+
127
+ def test_mail
128
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('mail From:<correct@example.com>'), "mail rejected good from?")
129
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('mail From: <correct@example.com>'), "mail rejected because of space after from?")
130
+ assert_equal(MailDiode::SMTPError::SYNTAX_MAIL, @engine.process_line('mail'), "mail without arg worked?")
131
+ assert_equal(MailDiode::SMTPError::SYNTAX_MAIL, @engine.process_line('mail foo'), "mail without From: worked?")
132
+ assert_equal(MailDiode::SMTPError::SYNTAX_MAIL, @engine.process_line('mail from:'), "mail without from arg worked?")
133
+ end
134
+
135
+ def test_rcpt
136
+ assert_equal(MailDiode::SMTPError::NEED_MAIL_BEFORE_RCPT, @engine.process_line('rcpt To:<correct@example.com>'), "rcpt before mail worked?")
137
+
138
+ @engine.process_line('mail From:<correct@example.com>')
139
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('rcpt To:<correct@example.com>'), "mail rejected good from?")
140
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('rcpt To: <correct@example.com>'), "mail rejected because of space after to?")
141
+ assert_equal(MailDiode::SMTPError::SYNTAX_RCPT, @engine.process_line('rcpt'), "mail without arg worked?")
142
+ assert_equal(MailDiode::SMTPError::SYNTAX_RCPT, @engine.process_line('rcpt foo'), "mail without To: worked?")
143
+ assert_equal(MailDiode::SMTPError::SYNTAX_RCPT, @engine.process_line('rcpt to:'), "mail without to arg worked?")
144
+
145
+ @engine.process_line('mail From:<correct@example.com>')
146
+ 99.times do
147
+ @engine.process_line('rcpt To:<correct@example.com>')
148
+ end
149
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('rcpt To:<correct@example.com>'), "100th recipient rejected?")
150
+ assert_equal(MailDiode::SMTPError::TOO_MANY_RECIPIENTS, @engine.process_line('rcpt To:<correct@example.com>'), "101st recipient not rejected?")
151
+ end
152
+
153
+ def test_data
154
+ assert_equal(MailDiode::SMTPError::NEED_RCPT_BEFORE_DATA, @engine.process_line('data'), "data before rcpt worked?")
155
+
156
+ @engine.process_line('mail From:<sender@example.com>')
157
+ @engine.process_line('rcpt To:<recipient@example.com>')
158
+ assert_equal(MailDiode::SMTPError::SYNTAX_DATA, @engine.process_line('data xxx'), "data with arg accepted?")
159
+ assert_equal(MailDiode::RESULT_DATA_OK, @engine.process_line('data'), "data rejected?")
160
+ assert_equal(nil, @engine.process_line('This is message data'), "responded to message data?")
161
+ @engine.process_line('Second line')
162
+ assert_equal(0, @engine.process_line('.').index(MailDiode::RESULT_OK), "didn't pretend to save?")
163
+ end
164
+
165
+
166
+
167
+
168
+
169
+ def test_filter
170
+ filter = DummyFilter.new
171
+ @engine.add_filter(filter)
172
+ @engine.process_line('mail From:<sender@example.com>')
173
+ @engine.process_line('rcpt To:<recipient@example.com>')
174
+ assert_equal('sender@example.com', filter.data.sender, "didn't call handler validate_sender?")
175
+ assert_equal('recipient@example.com', filter.data.recipient, "didn't call handler validate_recipient?")
176
+
177
+ filter.reject = "450 Try Later"
178
+ assert_equal(filter.reject, @engine.process_line('rcpt To:<recipient@example.com>'), "filter rejection not propagated?")
179
+ end
180
+
181
+ def test_mail_handler
182
+ @engine.process_line('mail From:<sender@example.com>')
183
+ @engine.process_line('rcpt To:<recipient@example.com>')
184
+
185
+ sample_text = ['First line', 'Second', '', ' . not a dot line', 'After blank']
186
+ @engine.process_line('DATA')
187
+ sample_text.each do | line |
188
+ @engine.process_line(line)
189
+ end
190
+ assert_equal(0, @engine.process_line('.').index(MailDiode::RESULT_OK), "handler didn't accept?")
191
+ assert_equal(sample_text.join("\r\n"), @handler.message_without_first_line, "bad message data?")
192
+
193
+ assert_equal(MailDiode::SMTPError::NEED_RCPT_BEFORE_DATA, @engine.process_line('data'), "didn't reset after .?")
194
+
195
+ MailDiode::set_log_level(Logger::INFO)
196
+ MailDiode::log_to_string
197
+
198
+ @handler.reject = true
199
+ @engine.process_line('mail From:<sender@example.com>')
200
+ assert_equal(MailDiode::SMTPError::UNKNOWN_RECIPIENT, @engine.process_line('rcpt To:<recipient@example.com>'), "reject to not propagated?")
201
+ end
202
+
203
+ def test_dot_in_data
204
+ @engine.process_line('mail From:<sender@example.com>')
205
+ @engine.process_line('rcpt To:<recipient@example.com>')
206
+ @engine.process_line('data')
207
+ line_with_dot = '..Line with a dot'
208
+ @engine.process_line(line_with_dot)
209
+ @engine.process_line('.')
210
+ assert_equal(line_with_dot[1..-1], @handler.message_without_first_line, "didn't unescape the dot?")
211
+ end
212
+
213
+ def test_args
214
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('noop '), "choked on trailing space?")
215
+ assert_equal(0, @engine.process_line("helo\tx").index('250'), "choked on tab?")
216
+ end
217
+
218
+ def test_case_insensitive_commands
219
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('noop'), "lower case failed?")
220
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('NOOP'), "upper case failed?")
221
+ assert_equal(MailDiode::RESULT_OK, @engine.process_line('nOOp'), "mixed case failed?")
222
+ end
223
+
224
+ def test_normal_logging
225
+ MailDiode::log_to_string
226
+ @engine.process_line('noop')
227
+ assert_equal('', MailDiode::get_log)
228
+ MailDiode::clear_log
229
+ @engine.process_line('xxx')
230
+ assert_equal('', MailDiode::get_log)
231
+ end
232
+
233
+ def test_verbose_logging
234
+ MailDiode::set_log_level(Logger::INFO)
235
+ MailDiode::log_to_string
236
+ @engine.process_line('noop')
237
+ assert(MailDiode::get_log.index('INFO'))
238
+ end
239
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright 2007-2008 Kevin B. Smith
4
+ # This file is part of MailDiode.
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3, as
8
+ # published by the Free Software Foundation.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'test/unit'
19
+
20
+ require 'test_engine.rb'
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maildiode
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Smith
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-03 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: daemons
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.10
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: gurgitate-mail
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.10.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: KirbyBase
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.6.0
44
+ version:
45
+ description:
46
+ email: kevins@qualitycode.com
47
+ executables:
48
+ - maildiode
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README
53
+ - doc/index.html
54
+ files:
55
+ - bin/maildiode
56
+ - lib/alias.rb
57
+ - lib/blacklist.rb
58
+ - lib/delay.rb
59
+ - lib/engine.rb
60
+ - lib/greylist.rb
61
+ - lib/maildir.rb
62
+ - lib/server.rb
63
+ - lib/util.rb
64
+ - lib/settings.rb
65
+ - test/test_engine.rb
66
+ - test/test_suite.rb
67
+ - README
68
+ - doc/index.html
69
+ has_rdoc: false
70
+ homepage: http://maildiode.rubyforge.org/
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 1.8.1
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project: maildiode
91
+ rubygems_version: 1.2.0
92
+ signing_key:
93
+ specification_version: 2
94
+ summary: MailDiode is a simple incoming SMTP server daemon.
95
+ test_files:
96
+ - test/test_suite.rb