maildiode 0.2.1 → 0.2.3
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 +15 -2
- data/bin/maildiode +2 -2
- data/lib/engine.rb +7 -2
- data/lib/server.rb +34 -11
- data/lib/settings.rb +11 -9
- data/maildiode.conf.sample +1 -0
- data/test/alias_spec.rb +71 -0
- data/test/engine_spec.rb +458 -0
- metadata +6 -5
- data/test/test_engine.rb +0 -239
- data/test/test_suite.rb +0 -20
data/README
CHANGED
@@ -22,7 +22,7 @@ FEATURES:
|
|
22
22
|
4. Full unit test suite
|
23
23
|
5. Runs in ruby SAFE mode for security
|
24
24
|
|
25
|
-
NON-FEATURES:
|
25
|
+
INTENTIONAL NON-FEATURES:
|
26
26
|
1. No outgoing mail
|
27
27
|
2. No TLS/SSL
|
28
28
|
3. No filtering, greylisting, aliases, etc. in the core
|
@@ -71,11 +71,24 @@ INSTALLATION AND CONFIGURATION
|
|
71
71
|
|
72
72
|
|
73
73
|
TODO
|
74
|
-
-
|
74
|
+
- Replace generic plugin system with:
|
75
|
+
alias - given a recipient, returns a recipient; called for RCPT
|
76
|
+
(probably doesn't need to be a plugin)
|
77
|
+
envelope_rejector - can reject based on envelope; called
|
78
|
+
for HELO/EHLO, MAIL, and RCPT
|
79
|
+
header_rejector - can reject message delivery based on envelope
|
80
|
+
plus email headers
|
81
|
+
message_modifier - given an envelope and entire message,
|
82
|
+
returns an entire message
|
83
|
+
delivery - given a recipient an entire message, delivers it or
|
84
|
+
rejects it
|
85
|
+
- Add rspec tests for each filter/plugin
|
75
86
|
- Blacklist: Should allow filtering on from, subject, etc
|
76
87
|
- Add debounce filter to reject bounces for mail we didn't send
|
77
88
|
- Greylist: Implement greylisting IP exemptions
|
78
89
|
- Greylist: Periodically clear out old database entries
|
90
|
+
- Allow integrating milters and/or other external filters
|
91
|
+
- Remove gurgitate dependency
|
79
92
|
- Properly handle multiple recipients where some work and some fail
|
80
93
|
(Reply OK but send bounces to the failures)
|
81
94
|
- Add a return path header?
|
data/bin/maildiode
CHANGED
@@ -28,9 +28,9 @@ module MailDiode
|
|
28
28
|
@settings = settings
|
29
29
|
end
|
30
30
|
|
31
|
-
def create_engine(hostname)
|
31
|
+
def create_engine(hostname, max_recipients)
|
32
32
|
|
33
|
-
engine = Engine.new(hostname)
|
33
|
+
engine = Engine.new(hostname, max_recipients)
|
34
34
|
|
35
35
|
engine.set_mail_handler(MaildirMessageHandler.new(@settings))
|
36
36
|
@plugins.each do | plugin |
|
data/lib/engine.rb
CHANGED
@@ -41,6 +41,7 @@ module MailDiode
|
|
41
41
|
attr_accessor :helo
|
42
42
|
attr_accessor :sender
|
43
43
|
attr_accessor :recipient
|
44
|
+
attr_accessor :original_recipient
|
44
45
|
end
|
45
46
|
|
46
47
|
class Filter
|
@@ -81,11 +82,14 @@ module MailDiode
|
|
81
82
|
|
82
83
|
# State machine that handles incoming SMTP commands
|
83
84
|
class Engine
|
84
|
-
|
85
|
+
attr_reader :envelope
|
86
|
+
|
87
|
+
def initialize(hostname, max_recipients)
|
85
88
|
@hostname = hostname
|
86
89
|
@greeting = "220 #{hostname} ESMTP"
|
87
90
|
@helo_response = "250 #{hostname}"
|
88
91
|
@filters = []
|
92
|
+
@max_recipients = max_recipients
|
89
93
|
end
|
90
94
|
|
91
95
|
def set_mail_handler(handler)
|
@@ -186,7 +190,7 @@ module MailDiode
|
|
186
190
|
if(! @envelope)
|
187
191
|
raise_smtp_error(SMTPError::NEED_MAIL_BEFORE_RCPT)
|
188
192
|
end
|
189
|
-
if(@envelope.recipients.size >=
|
193
|
+
if(@envelope.recipients.size >= @max_recipients)
|
190
194
|
raise_smtp_error(SMTPError::TOO_MANY_RECIPIENTS)
|
191
195
|
end
|
192
196
|
recipient = enforce_address_arg(args, 'to', SMTPError::SYNTAX_RCPT)
|
@@ -222,6 +226,7 @@ module MailDiode
|
|
222
226
|
filterable_data.helo = @helo
|
223
227
|
filterable_data.sender = sender
|
224
228
|
filterable_data.recipient = recipient
|
229
|
+
filterable_data.original_recipient = recipient
|
225
230
|
@filters.each do | filter |
|
226
231
|
if filter.respond_to? :process
|
227
232
|
filter.process(filterable_data)
|
data/lib/server.rb
CHANGED
@@ -28,16 +28,14 @@ module MailDiode
|
|
28
28
|
|
29
29
|
@factory = engine_factory
|
30
30
|
super(@port, @ip, @max_connections)
|
31
|
+
@audit = true
|
31
32
|
end
|
32
33
|
|
33
34
|
def serve(client)
|
34
35
|
begin
|
35
|
-
MailDiode::log_debug("
|
36
|
-
if(connections > 1)
|
37
|
-
MailDiode::log_info("Connections: #{connections}")
|
38
|
-
end
|
36
|
+
MailDiode::log_debug("serve")
|
39
37
|
client.binmode
|
40
|
-
engine = @factory.create_engine(@hostname)
|
38
|
+
engine = @factory.create_engine(@hostname, @max_recipients)
|
41
39
|
family, port, hostname, ip = client.addr
|
42
40
|
greeting = engine.start(ip)
|
43
41
|
MailDiode::log_debug("Greeting: #{greeting}")
|
@@ -56,9 +54,11 @@ module MailDiode
|
|
56
54
|
end
|
57
55
|
end
|
58
56
|
rescue Errno::ECONNRESET
|
59
|
-
MailDiode::log_debug "Client
|
57
|
+
MailDiode::log_debug "Client reset connection"
|
60
58
|
rescue Errno::ETIMEDOUT
|
61
59
|
MailDiode::log_info "Client input timeout"
|
60
|
+
rescue Errno::ENOTCONN
|
61
|
+
MailDiode::log_info "Client dropped connection"
|
62
62
|
rescue Exception => e
|
63
63
|
MailDiode::log_error("Exception #{e.class}: #{e}")
|
64
64
|
e.backtrace.each do | line |
|
@@ -66,8 +66,24 @@ module MailDiode
|
|
66
66
|
end
|
67
67
|
client << response("451 Internal Server Error")
|
68
68
|
end
|
69
|
+
|
69
70
|
end
|
70
|
-
|
71
|
+
|
72
|
+
def connecting(client)
|
73
|
+
log_new_connection(client)
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def log_new_connection(client)
|
78
|
+
addr = client.peeraddr
|
79
|
+
show = "#{addr[1]} #{addr[2]}<#{addr[3]}>"
|
80
|
+
MailDiode::log_info("connecting: #{show} (#{connections})")
|
81
|
+
end
|
82
|
+
|
83
|
+
def disconnecting(clientPort)
|
84
|
+
MailDiode::log_info("disconnecting: #{clientPort} (#{connections})")
|
85
|
+
end
|
86
|
+
|
71
87
|
def get_line(input, timeout_seconds)
|
72
88
|
line = nil
|
73
89
|
t = Thread.new { line = input.gets(NEWLINE) }
|
@@ -76,11 +92,16 @@ module MailDiode
|
|
76
92
|
end
|
77
93
|
|
78
94
|
def discard_early_input(client)
|
79
|
-
|
80
|
-
|
95
|
+
t = Thread.new { consume_all_input(client) }
|
96
|
+
t.join(TIMEOUT_SECONDS)
|
97
|
+
end
|
98
|
+
|
99
|
+
def consume_all_input(input)
|
100
|
+
while(input.ready?)
|
101
|
+
input.getc
|
81
102
|
end
|
82
103
|
end
|
83
|
-
|
104
|
+
|
84
105
|
def response(text)
|
85
106
|
return "#{text}#{NEWLINE}"
|
86
107
|
end
|
@@ -92,10 +113,12 @@ module MailDiode
|
|
92
113
|
@port = settings.get_int('server', 'port', DEFAULT_PORT)
|
93
114
|
@port.untaint
|
94
115
|
@max_connections = settings.get_int('server', 'maxconnections', DEFAULT_MAX_CONNECTIONS)
|
116
|
+
@max_recipients = settings.get_int('server', 'maxrecipients', DEFAULT_MAX_RECIPIENTS)
|
95
117
|
end
|
96
118
|
|
97
119
|
DEFAULT_PORT = 10025
|
98
|
-
DEFAULT_MAX_CONNECTIONS = 20
|
120
|
+
DEFAULT_MAX_CONNECTIONS = 20
|
121
|
+
DEFAULT_MAX_RECIPIENTS = 5
|
99
122
|
TIMEOUT_SECONDS = 1200 # djb recommends 1200
|
100
123
|
end
|
101
124
|
end
|
data/lib/settings.rb
CHANGED
@@ -51,15 +51,17 @@ module MailDiode
|
|
51
51
|
end
|
52
52
|
|
53
53
|
class Settings
|
54
|
-
def initialize(config_file)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
def initialize(config_file = nil)
|
55
|
+
@settings = {}
|
56
|
+
if(config_file)
|
57
|
+
File.foreach(config_file) do | line |
|
58
|
+
line = line.gsub(/#.*/, '').strip
|
59
|
+
if !line.empty?
|
60
|
+
component, keyword, args = line.split(' ', 3)
|
61
|
+
load_setting(component.downcase, keyword.downcase, args)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
63
65
|
end
|
64
66
|
|
65
67
|
def load_setting(component, keyword, args)
|
data/maildiode.conf.sample
CHANGED
data/test/alias_spec.rb
ADDED
@@ -0,0 +1,71 @@
|
|
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 'spec'
|
19
|
+
|
20
|
+
require 'util'
|
21
|
+
require 'engine'
|
22
|
+
require 'maildiode-plugins/alias'
|
23
|
+
require 'settings'
|
24
|
+
|
25
|
+
describe MailDiode::AliasFilter do
|
26
|
+
describe "Basic Settings" do
|
27
|
+
before(:each) do
|
28
|
+
settings = MailDiode::Settings.new
|
29
|
+
settings.load_setting('alias', 'from', 'to')
|
30
|
+
settings.load_setting('alias', 'alsofrom', 'to')
|
31
|
+
settings.load_setting('alias', '\d\d', 'digits')
|
32
|
+
@filter = MailDiode::AliasFilter.new(settings)
|
33
|
+
@filterable = MailDiode::FilterableData.new
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should perform a simple alias replacement" do
|
37
|
+
@filterable.recipient = 'from'
|
38
|
+
@filter.process(@filterable)
|
39
|
+
@filterable.recipient.should == 'to'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should allow a second alias to the same destination" do
|
43
|
+
@filterable = MailDiode::FilterableData.new
|
44
|
+
@filterable.recipient = 'alsofrom'
|
45
|
+
@filter.process(@filterable)
|
46
|
+
@filterable.recipient.should == 'to'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should ignore case" do
|
50
|
+
@filterable.recipient = 'FROM'
|
51
|
+
@filter.process(@filterable)
|
52
|
+
@filterable.recipient.should == 'to'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should allow REGEXP aliases" do
|
56
|
+
@filterable = MailDiode::FilterableData.new
|
57
|
+
@filterable.recipient = '27'
|
58
|
+
@filter.process(@filterable)
|
59
|
+
@filterable.recipient.should == 'digits'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not affect original_recipient" do
|
63
|
+
@filterable = MailDiode::FilterableData.new
|
64
|
+
@filterable.original_recipient = 'untouched'
|
65
|
+
@filterable.recipient = 'from'
|
66
|
+
@filter.process(@filterable)
|
67
|
+
@filterable.original_recipient.should == 'untouched'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/test/engine_spec.rb
ADDED
@@ -0,0 +1,458 @@
|
|
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 'spec'
|
19
|
+
|
20
|
+
require 'util'
|
21
|
+
require 'engine'
|
22
|
+
|
23
|
+
FAKE_IP = '10.10.10.10'
|
24
|
+
FAKE_HOSTNAME = 'woohoo'
|
25
|
+
MAX_RECIPIENTS = 7
|
26
|
+
|
27
|
+
class DummyFilter < MailDiode::Filter
|
28
|
+
attr_accessor :reject
|
29
|
+
attr_accessor :reject_process
|
30
|
+
attr_accessor :alias
|
31
|
+
attr_reader :data
|
32
|
+
attr_reader :helo_text
|
33
|
+
attr_reader :mail_text
|
34
|
+
attr_reader :rcpt_text
|
35
|
+
|
36
|
+
def helo(host)
|
37
|
+
if(reject)
|
38
|
+
raise MailDiode::SMTPError.new(reject)
|
39
|
+
end
|
40
|
+
@helo_text = host
|
41
|
+
end
|
42
|
+
|
43
|
+
def mail(sender)
|
44
|
+
if(reject)
|
45
|
+
raise MailDiode::SMTPError.new(reject)
|
46
|
+
end
|
47
|
+
@mail_text = sender
|
48
|
+
end
|
49
|
+
|
50
|
+
def rcpt(recipient)
|
51
|
+
if(reject)
|
52
|
+
raise MailDiode::SMTPError.new(reject)
|
53
|
+
end
|
54
|
+
@rcpt_text = recipient
|
55
|
+
end
|
56
|
+
|
57
|
+
def process(filterable_data)
|
58
|
+
if(reject_process)
|
59
|
+
raise MailDiode::SMTPError.new(reject_process)
|
60
|
+
end
|
61
|
+
filterable_data.recipient = @alias
|
62
|
+
@data = filterable_data
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
$SAMPLE_MAIL_ID = '12345'
|
67
|
+
|
68
|
+
class DummyMailHandler < MailDiode::MailHandler
|
69
|
+
attr_reader :message_text
|
70
|
+
attr_accessor :reject
|
71
|
+
|
72
|
+
def initialize
|
73
|
+
@to = []
|
74
|
+
valid = true
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid_recipient?(to_address)
|
78
|
+
return !reject
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_message(original_recipient, recipient, text_lines)
|
82
|
+
@message_text = text_lines
|
83
|
+
return $SAMPLE_MAIL_ID
|
84
|
+
end
|
85
|
+
|
86
|
+
def message_without_first_line
|
87
|
+
return message_text.split("\r\n")[1..-1].join("\r\n")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe MailDiode::Engine do
|
92
|
+
before(:each) do
|
93
|
+
MailDiode::set_log_level(Logger::WARN)
|
94
|
+
MailDiode::log_to_console
|
95
|
+
|
96
|
+
@engine = MailDiode::Engine.new(FAKE_HOSTNAME, MAX_RECIPIENTS)
|
97
|
+
@handler = DummyMailHandler.new
|
98
|
+
@engine.set_mail_handler(@handler)
|
99
|
+
|
100
|
+
@greeting = @engine.start(FAKE_IP)
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "Normal Logging" do
|
104
|
+
it "should log nothing for a valid command" do
|
105
|
+
MailDiode::log_to_string
|
106
|
+
@engine.process_line('noop')
|
107
|
+
MailDiode::get_log.should == ''
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should log nothing for a bad command" do
|
111
|
+
MailDiode::log_to_string
|
112
|
+
@engine.process_line('xxx')
|
113
|
+
MailDiode::get_log.should == ''
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "Verbose Logging" do
|
118
|
+
it "should log every command when verbose" do
|
119
|
+
MailDiode::set_log_level(Logger::DEBUG)
|
120
|
+
MailDiode::log_to_string
|
121
|
+
@engine.process_line('noop')
|
122
|
+
(MailDiode::get_log.index('DEBUG')>0).should == true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "Greeting" do
|
127
|
+
it "should start its greeting with 220" do
|
128
|
+
@greeting.should == "220 #{FAKE_HOSTNAME} ESMTP"
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should include hostname in its greeting" do
|
132
|
+
@greeting.index(FAKE_HOSTNAME).should_not == nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "Unknown commands" do
|
137
|
+
it "should reject a blank command" do
|
138
|
+
@engine.process_line('').index(MailDiode::SMTPError::BAD_COMMAND).should == 0
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should reject an unknown command" do
|
142
|
+
@engine.process_line('xxx').index(MailDiode::SMTPError::BAD_COMMAND).should == 0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "Commands" do
|
147
|
+
describe "NOOP" do
|
148
|
+
it "should allow NOOP in upper or mixed or lower case" do
|
149
|
+
@engine.process_line('NOOP').should == MailDiode::RESULT_OK
|
150
|
+
@engine.process_line('Noop').should == MailDiode::RESULT_OK
|
151
|
+
@engine.process_line('noop').should == MailDiode::RESULT_OK
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should allow trailing space" do
|
155
|
+
@engine.process_line('noop ').should == MailDiode::RESULT_OK
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should disallow a parameter for NOOP" do
|
159
|
+
@engine.process_line('noop x').should == MailDiode::SMTPError::SYNTAX_NOOP
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "QUIT" do
|
164
|
+
it "should say BYE in response to QUIT" do
|
165
|
+
@engine.process_line('quit').should == MailDiode::RESULT_BYE
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should terminate after getting a QUIT" do
|
169
|
+
@engine.should_not be_terminate
|
170
|
+
@engine.process_line('quit')
|
171
|
+
@engine.should be_terminate
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should disallow a parameter for QUIT" do
|
175
|
+
@engine.process_line('quit x').should == MailDiode::SMTPError::SYNTAX_QUIT
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "HELO without parameters" do
|
180
|
+
it "should disallow HELO and EHLO without a parameter" do
|
181
|
+
@engine.process_line('helo').should == MailDiode::SMTPError::SYNTAX_HELO
|
182
|
+
@engine.process_line('ehlo').should == MailDiode::SMTPError::SYNTAX_HELO
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "HELO" do
|
187
|
+
it "should reply with 250 to a simple HELO" do
|
188
|
+
@engine.process_line('helo localhost').index('250').should == 0
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should reply the same for each HELO or EHLO" do
|
192
|
+
reply = @engine.process_line('helo localhost')
|
193
|
+
@engine.process_line('helo other').should == reply
|
194
|
+
@engine.process_line('ehlo 1.2.3.4').should == reply
|
195
|
+
@engine.process_line('helo some.domain.name').should == reply
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should allow tab before parameter" do
|
199
|
+
@engine.process_line("helo\tx").index('250').should == 0
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "RSET" do
|
205
|
+
it "should allow a simple initial RSET" do
|
206
|
+
@engine.process_line('rset').should == MailDiode::RESULT_OK
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should disallow a parameter for RSET" do
|
210
|
+
@engine.process_line('rset x').should == MailDiode::SMTPError::SYNTAX_RSET
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should clear FROM when it gets a RSET" do
|
214
|
+
@engine.process_line('mail From:<sender@example.com>')
|
215
|
+
@engine.process_line('rset')
|
216
|
+
result = @engine.process_line('rcpt To:<recipient@example.com>')
|
217
|
+
result.should == MailDiode::SMTPError::NEED_MAIL_BEFORE_RCPT
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe "VRFY" do
|
222
|
+
it "should disallow VRFY without a parameter" do
|
223
|
+
@engine.process_line('vrfy').should == MailDiode::SMTPError::SYNTAX_VRFY
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should respond with UNSURE for any VRFY request" do
|
227
|
+
@engine.process_line('vrfy anything').should == MailDiode::RESULT_UNSURE
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "MAIL" do
|
232
|
+
before(:each) do
|
233
|
+
@valid_sender = "correct@example.com"
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should disallow MAIL with no parameters" do
|
237
|
+
@engine.process_line('mail').should == MailDiode::SMTPError::SYNTAX_MAIL
|
238
|
+
end
|
239
|
+
it "should disallow MAIL without from:" do
|
240
|
+
@engine.process_line('mail foo').should == MailDiode::SMTPError::SYNTAX_MAIL
|
241
|
+
end
|
242
|
+
it "should disallow MAIL that doesn't specify an address after from:" do
|
243
|
+
@engine.process_line('mail from:').should == MailDiode::SMTPError::SYNTAX_MAIL
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should allow mail from:address" do
|
247
|
+
result = @engine.process_line("mail From:#{@valid_sender}")
|
248
|
+
result.should == MailDiode::RESULT_OK
|
249
|
+
@engine.envelope.sender.should == @valid_sender
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should strip < > from around sender email" do
|
253
|
+
@engine.process_line("mail From:<#{@valid_sender}>")
|
254
|
+
@engine.envelope.sender.should == @valid_sender
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should allow space between from: and the sender" do
|
258
|
+
@engine.process_line("mail From: #{@valid_sender}").should == MailDiode::RESULT_OK
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should allow a second MAIL command in a row" do
|
262
|
+
@engine.process_line('mail From:Someone')
|
263
|
+
result = @engine.process_line("mail From:#{@valid_sender}")
|
264
|
+
result.should == MailDiode::RESULT_OK
|
265
|
+
@engine.envelope.sender.should == @valid_sender
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
describe "RCPT before MAIL" do
|
270
|
+
it "should disallow RCPT if no sender has been specified" do
|
271
|
+
@engine.process_line('rcpt To:<correct@example.com>').should == MailDiode::SMTPError::NEED_MAIL_BEFORE_RCPT
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "RCPT" do
|
276
|
+
before(:each) do
|
277
|
+
@valid_recipient = 'correct@example.com'
|
278
|
+
@engine.process_line('mail From:Someone')
|
279
|
+
@engine.envelope.recipients.size.should == 0
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should disallow RCPT with no parameters" do
|
283
|
+
@engine.process_line('rcpt').should == MailDiode::SMTPError::SYNTAX_RCPT
|
284
|
+
end
|
285
|
+
it "should disallow RCPT without to:" do
|
286
|
+
@engine.process_line('rcpt foo').should == MailDiode::SMTPError::SYNTAX_RCPT
|
287
|
+
end
|
288
|
+
it "should disallow RCPT that doesn't specify an address after to:" do
|
289
|
+
@engine.process_line('rcpt to:').should == MailDiode::SMTPError::SYNTAX_RCPT
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should allow RCPT to:address" do
|
293
|
+
result = @engine.process_line("rcpt To:#{@valid_recipient}")
|
294
|
+
result.should == MailDiode::RESULT_OK
|
295
|
+
@engine.envelope.recipients.size.should == 1
|
296
|
+
@engine.envelope.recipients[0].should == @valid_recipient
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should allow space after to: in RCPT" do
|
300
|
+
result = @engine.process_line("rcpt To: #{@valid_recipient}")
|
301
|
+
result.should == MailDiode::RESULT_OK
|
302
|
+
@engine.envelope.recipients.size.should == 1
|
303
|
+
@engine.envelope.recipients[0].should == @valid_recipient
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should remove < > from recipient address" do
|
307
|
+
result = @engine.process_line("rcpt To:<#{@valid_recipient}>")
|
308
|
+
result.should == MailDiode::RESULT_OK
|
309
|
+
@engine.envelope.recipients.size.should == 1
|
310
|
+
@engine.envelope.recipients[0].should == @valid_recipient
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should clear recipients when a second FROM is processed" do
|
314
|
+
@engine.process_line("rcpt To:#{@valid_recipient}")
|
315
|
+
@engine.process_line('mail From:Someone')
|
316
|
+
@engine.envelope.recipients.size.should == 0
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should fail if there are too many recipients" do
|
320
|
+
(MAX_RECIPIENTS-1).times do
|
321
|
+
@engine.process_line("rcpt To:#{@valid_recipient}")
|
322
|
+
end
|
323
|
+
@engine.process_line("rcpt To:#{@valid_recipient}").should == MailDiode::RESULT_OK
|
324
|
+
@engine.process_line("rcpt To:#{@valid_recipient}").should == MailDiode::SMTPError::TOO_MANY_RECIPIENTS
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "DATA before RCPT" do
|
329
|
+
it "should disallow DATA before RCPT" do
|
330
|
+
@engine.process_line('data').should == MailDiode::SMTPError::NEED_RCPT_BEFORE_DATA
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe "DATA" do
|
335
|
+
before(:each) do
|
336
|
+
@engine.process_line('mail From:Someone')
|
337
|
+
@engine.process_line("rcpt To:Someone")
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should disallow DATA with a parameter" do
|
341
|
+
@engine.process_line('data xxx').should == MailDiode::SMTPError::SYNTAX_DATA
|
342
|
+
end
|
343
|
+
|
344
|
+
it "should allow lines of message text after DATA" do
|
345
|
+
@engine.process_line('data').should == MailDiode::RESULT_DATA_OK
|
346
|
+
@engine.process_line('This is message data').should == nil
|
347
|
+
@engine.process_line('Second line').should == nil
|
348
|
+
@engine.process_line('.').should == "#{MailDiode::RESULT_OK} #{$SAMPLE_MAIL_ID}"
|
349
|
+
end
|
350
|
+
|
351
|
+
it "should allow escaped dot at start of message text line" do
|
352
|
+
@engine.process_line('data')
|
353
|
+
line_with_dot = '..Line with a dot'
|
354
|
+
@engine.process_line(line_with_dot)
|
355
|
+
@engine.process_line('.')
|
356
|
+
@handler.message_without_first_line.should == line_with_dot[1..-1]
|
357
|
+
end
|
358
|
+
|
359
|
+
it "should reset sender after data" do
|
360
|
+
@engine.process_line('data')
|
361
|
+
@engine.process_line('.')
|
362
|
+
@engine.process_line('data').should == MailDiode::SMTPError::NEED_RCPT_BEFORE_DATA
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe "Filter" do
|
368
|
+
before(:each) do
|
369
|
+
@filter = DummyFilter.new
|
370
|
+
@engine.add_filter(@filter)
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should call filter#helo for HELO/EHLO" do
|
374
|
+
host = 'test'
|
375
|
+
@engine.process_line("helo #{host}")
|
376
|
+
@filter.helo_text.should == host
|
377
|
+
host = 'another'
|
378
|
+
@engine.process_line("ehlo #{host}")
|
379
|
+
@filter.helo_text.should == host
|
380
|
+
end
|
381
|
+
|
382
|
+
it "should allow filter to reject HELO" do
|
383
|
+
@filter.reject = "450 Try Later"
|
384
|
+
@engine.process_line('helo test').should == @filter.reject
|
385
|
+
end
|
386
|
+
|
387
|
+
it "should call filter#mail for MAIL" do
|
388
|
+
sender = 'someone'
|
389
|
+
@engine.process_line("mail from:#{sender}")
|
390
|
+
@filter.mail_text.should == sender
|
391
|
+
end
|
392
|
+
|
393
|
+
it "should allow filter to reject MAIL" do
|
394
|
+
@filter.reject = "450 Try Later"
|
395
|
+
@engine.process_line('mail from: test').should == @filter.reject
|
396
|
+
end
|
397
|
+
|
398
|
+
it "should not call filter#process for MAIL command" do
|
399
|
+
@engine.process_line('mail From:<sender@example.com>')
|
400
|
+
@filter.data.should == nil
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should call filter#rcpt for RCPT" do
|
404
|
+
recipient = 'whoever'
|
405
|
+
@engine.process_line("mail from:someone")
|
406
|
+
@engine.process_line("rcpt to:#{recipient}").should == MailDiode::RESULT_OK
|
407
|
+
@filter.rcpt_text.should == recipient
|
408
|
+
end
|
409
|
+
|
410
|
+
it "should allow filter to reject RCPT" do
|
411
|
+
@engine.process_line('mail From:<sender@example.com>')
|
412
|
+
@filter.reject = "450 Try Later"
|
413
|
+
@engine.process_line('rcpt To:<recipient@example.com>').should == @filter.reject
|
414
|
+
end
|
415
|
+
|
416
|
+
it "should call filter#process for RCPT command" do
|
417
|
+
sender = 'sender@example.com'
|
418
|
+
@filter.alias = 'real_recipient'
|
419
|
+
recipient = 'recipient@example.com'
|
420
|
+
@engine.process_line("mail From:#{sender}")
|
421
|
+
@engine.process_line("rcpt To:#{recipient}")
|
422
|
+
@filter.data.sender_ip == FAKE_IP
|
423
|
+
@filter.data.helo == FAKE_HOSTNAME
|
424
|
+
@filter.data.sender.should == sender
|
425
|
+
@filter.data.recipient.should == @filter.alias
|
426
|
+
@filter.data.original_recipient.should == recipient
|
427
|
+
end
|
428
|
+
|
429
|
+
it "should allow filter#process to reject" do
|
430
|
+
@engine.process_line('mail From:<sender@example.com>')
|
431
|
+
@filter.reject_process = "450 Try process Later"
|
432
|
+
@engine.process_line('rcpt To:<recipient@example.com>').should == @filter.reject_process
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
describe "Mail Handler" do
|
437
|
+
it "should pass the message to the handler" do
|
438
|
+
@engine.process_line('mail From:<sender@example.com>')
|
439
|
+
@engine.process_line('rcpt To:<recipient@example.com>')
|
440
|
+
|
441
|
+
sample_text = ['First line', 'Second', '', ' . not a dot line', 'After blank']
|
442
|
+
@engine.process_line('DATA')
|
443
|
+
sample_text.each do | line |
|
444
|
+
@engine.process_line(line)
|
445
|
+
end
|
446
|
+
@engine.process_line('.').index(MailDiode::RESULT_OK).should == 0
|
447
|
+
@handler.message_without_first_line.should == sample_text.join("\r\n")
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should allow message handler to reject RCPT" do
|
451
|
+
address = 'sender@example.com'
|
452
|
+
@handler.reject = true
|
453
|
+
@engine.process_line("mail From:Someone")
|
454
|
+
error = "#{MailDiode::SMTPError::UNKNOWN_RECIPIENT}: #{address}"
|
455
|
+
@engine.process_line("rcpt To:#{address}").should == error
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maildiode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Smith
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-04-05 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -52,8 +52,8 @@ files:
|
|
52
52
|
- lib/maildiode-plugins/alias.rb
|
53
53
|
- lib/maildiode-plugins/blacklist.rb
|
54
54
|
- lib/maildiode-plugins/delay.rb
|
55
|
-
- test/
|
56
|
-
- test/
|
55
|
+
- test/alias_spec.rb
|
56
|
+
- test/engine_spec.rb
|
57
57
|
- maildiode.conf.sample
|
58
58
|
- README
|
59
59
|
- COPYING
|
@@ -85,4 +85,5 @@ signing_key:
|
|
85
85
|
specification_version: 2
|
86
86
|
summary: MailDiode is a simple incoming SMTP server daemon.
|
87
87
|
test_files:
|
88
|
-
- test/
|
88
|
+
- test/engine_spec.rb
|
89
|
+
- test/alias_spec.rb
|
data/test/test_engine.rb
DELETED
@@ -1,239 +0,0 @@
|
|
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
|
data/test/test_suite.rb
DELETED
@@ -1,20 +0,0 @@
|
|
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'
|