maildiode 0.2.3 → 0.2.5
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/lib/engine.rb +20 -3
- data/lib/maildiode-plugins/blacklist.rb +1 -1
- data/lib/server.rb +26 -20
- data/maildiode.conf.sample +1 -0
- data/test/engine_spec.rb +23 -0
- metadata +2 -2
data/lib/engine.rb
CHANGED
@@ -83,6 +83,9 @@ module MailDiode
|
|
83
83
|
# State machine that handles incoming SMTP commands
|
84
84
|
class Engine
|
85
85
|
attr_reader :envelope
|
86
|
+
attr_accessor :max_data_lines
|
87
|
+
|
88
|
+
MAX_ALLOWED_REJECTS = 1
|
86
89
|
|
87
90
|
def initialize(hostname, max_recipients)
|
88
91
|
@hostname = hostname
|
@@ -90,6 +93,8 @@ module MailDiode
|
|
90
93
|
@helo_response = "250 #{hostname}"
|
91
94
|
@filters = []
|
92
95
|
@max_recipients = max_recipients
|
96
|
+
@rejected_count = 0
|
97
|
+
@max_data_lines = 1000000
|
93
98
|
end
|
94
99
|
|
95
100
|
def set_mail_handler(handler)
|
@@ -112,11 +117,14 @@ module MailDiode
|
|
112
117
|
end
|
113
118
|
|
114
119
|
command, args = extract_args(line)
|
115
|
-
|
116
|
-
|
117
|
-
|
120
|
+
result = process_command(command, args)
|
121
|
+
MailDiode::log_success(command, args, result)
|
122
|
+
return result
|
118
123
|
rescue SMTPError => error
|
119
124
|
MailDiode.log_info(error.text)
|
125
|
+
if(error.text.index(SMTPError::TOO_MANY_REJECTS) == 0)
|
126
|
+
@should_terminate = true
|
127
|
+
end
|
120
128
|
return error.text
|
121
129
|
end
|
122
130
|
|
@@ -201,6 +209,10 @@ module MailDiode
|
|
201
209
|
end
|
202
210
|
filterable_data = apply_filters(@envelope.sender, recipient)
|
203
211
|
if !@mail_handler.valid_recipient?(filterable_data.recipient)
|
212
|
+
if @rejected_count >= MAX_ALLOWED_REJECTS
|
213
|
+
raise_smtp_error(SMTPError::TOO_MANY_REJECTS)
|
214
|
+
end
|
215
|
+
@rejected_count += 1
|
204
216
|
raise_smtp_error(SMTPError::UNKNOWN_RECIPIENT + ": " + recipient)
|
205
217
|
end
|
206
218
|
@envelope.add_recipient(recipient)
|
@@ -244,6 +256,9 @@ module MailDiode
|
|
244
256
|
line = line[1..-1]
|
245
257
|
end
|
246
258
|
|
259
|
+
if @message_text.size >= max_data_lines
|
260
|
+
raise_smtp_error(SMTPError::MESSAGE_TOO_LONG)
|
261
|
+
end
|
247
262
|
@message_text << line
|
248
263
|
return nil
|
249
264
|
end
|
@@ -342,8 +357,10 @@ module MailDiode
|
|
342
357
|
SYNTAX_DATA = "501 Syntax: DATA"
|
343
358
|
BAD_COMMAND = '502 Error: command not implemented'
|
344
359
|
TOO_MANY_RECIPIENTS = '552 Error: Too many recipients'
|
360
|
+
TOO_MANY_REJECTS = '552 Error: Too many illegal recipients'
|
345
361
|
NEED_MAIL_BEFORE_RCPT = '503 Error: need MAIL before RCPT'
|
346
362
|
NEED_RCPT_BEFORE_DATA = '503 Error: need RCPT before DATA'
|
347
363
|
UNKNOWN_RECIPIENT = "550 Error: No such recipient here"
|
364
|
+
MESSAGE_TOO_LONG = "552 Error: Message too long"
|
348
365
|
end
|
349
366
|
end
|
@@ -30,7 +30,7 @@ module MailDiode
|
|
30
30
|
@blacklisted_recipients.each do | pattern |
|
31
31
|
re = Regexp.new(pattern, Regexp::IGNORECASE)
|
32
32
|
if(recipient.match(re))
|
33
|
-
raise MailDiode::SMTPError.new(MailDiode::SMTPError::
|
33
|
+
raise MailDiode::SMTPError.new(MailDiode::SMTPError::TOO_MANY_REJECTS + ": " + recipient)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
data/lib/server.rb
CHANGED
@@ -33,7 +33,7 @@ module MailDiode
|
|
33
33
|
|
34
34
|
def serve(client)
|
35
35
|
begin
|
36
|
-
MailDiode::log_debug("serve")
|
36
|
+
MailDiode::log_debug("##{client_id(client)}: serve")
|
37
37
|
client.binmode
|
38
38
|
engine = @factory.create_engine(@hostname, @max_recipients)
|
39
39
|
family, port, hostname, ip = client.addr
|
@@ -41,47 +41,47 @@ module MailDiode
|
|
41
41
|
MailDiode::log_debug("Greeting: #{greeting}")
|
42
42
|
client << response(greeting)
|
43
43
|
while !engine.terminate?
|
44
|
-
|
44
|
+
MailDiode::log_debug("##{client_id(client)}: calling get_line")
|
45
|
+
line = get_line(client, @timeout_seconds)
|
46
|
+
MailDiode::log_debug("##{client_id(client)}: returned from get_line")
|
45
47
|
if(!line)
|
46
|
-
|
47
|
-
return
|
48
|
+
break
|
48
49
|
end
|
49
50
|
line.chomp!(NEWLINE)
|
51
|
+
MailDiode::log_debug("##{client_id(client)}: calling process_line")
|
50
52
|
result = engine.process_line(line)
|
53
|
+
MailDiode::log_debug("##{client_id(client)}: returned from process_line")
|
51
54
|
if(result)
|
55
|
+
MailDiode::log_debug("##{client_id(client)}: calling discard_early_input")
|
52
56
|
discard_early_input(client)
|
57
|
+
MailDiode::log_debug("##{client_id(client)}: returned from discard_early_input")
|
53
58
|
client << response(result)
|
54
59
|
end
|
55
60
|
end
|
61
|
+
MailDiode::log_debug "##{client_id(client)}: engine terminated"
|
56
62
|
rescue Errno::ECONNRESET
|
57
|
-
MailDiode::
|
63
|
+
MailDiode::log_info "##{client_id(client)}: Client reset connection"
|
58
64
|
rescue Errno::ETIMEDOUT
|
59
|
-
MailDiode::log_info "Client input timeout"
|
65
|
+
MailDiode::log_info "##{client_id(client)}: Client input timeout"
|
60
66
|
rescue Errno::ENOTCONN
|
61
|
-
MailDiode::log_info "Client dropped connection"
|
67
|
+
MailDiode::log_info "##{client_id(client)}: Client dropped connection"
|
62
68
|
rescue Exception => e
|
63
|
-
MailDiode::log_error("Exception #{e.class}: #{e}")
|
69
|
+
MailDiode::log_error("##{client_id(client)}: Exception #{e.class}: #{e}")
|
64
70
|
e.backtrace.each do | line |
|
65
71
|
MailDiode::log_error(line)
|
66
72
|
end
|
67
73
|
client << response("451 Internal Server Error")
|
68
74
|
end
|
69
|
-
|
75
|
+
MailDiode::log_debug "##{client_id(client)}: serve exiting"
|
70
76
|
end
|
71
77
|
|
72
78
|
def connecting(client)
|
73
|
-
|
79
|
+
MailDiode::log_debug("##{client_id(client)}: connecting #{client.peeraddr[2]} -> #{client.peeraddr[3]}")
|
74
80
|
true
|
75
81
|
end
|
76
82
|
|
77
|
-
def
|
78
|
-
|
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})")
|
83
|
+
def disconnecting(client_port)
|
84
|
+
MailDiode::log_debug("##{client_port}: disconnecting (#{connections})")
|
85
85
|
end
|
86
86
|
|
87
87
|
def get_line(input, timeout_seconds)
|
@@ -93,7 +93,7 @@ module MailDiode
|
|
93
93
|
|
94
94
|
def discard_early_input(client)
|
95
95
|
t = Thread.new { consume_all_input(client) }
|
96
|
-
t.join(
|
96
|
+
t.join(@timeout_seconds)
|
97
97
|
end
|
98
98
|
|
99
99
|
def consume_all_input(input)
|
@@ -114,11 +114,17 @@ module MailDiode
|
|
114
114
|
@port.untaint
|
115
115
|
@max_connections = settings.get_int('server', 'maxconnections', DEFAULT_MAX_CONNECTIONS)
|
116
116
|
@max_recipients = settings.get_int('server', 'maxrecipients', DEFAULT_MAX_RECIPIENTS)
|
117
|
+
@timeout_seconds = settings.get_int('server', 'timeoutseconds', DEFAULT_TIMEOUT_SECONDS)
|
118
|
+
end
|
119
|
+
|
120
|
+
def client_id(client)
|
121
|
+
addr = client.peeraddr
|
122
|
+
return "#{addr[1]}"
|
117
123
|
end
|
118
124
|
|
119
125
|
DEFAULT_PORT = 10025
|
120
126
|
DEFAULT_MAX_CONNECTIONS = 20
|
121
127
|
DEFAULT_MAX_RECIPIENTS = 5
|
122
|
-
|
128
|
+
DEFAULT_TIMEOUT_SECONDS = 120 # djb recommends 1200
|
123
129
|
end
|
124
130
|
end
|
data/maildiode.conf.sample
CHANGED
data/test/engine_spec.rb
CHANGED
@@ -88,6 +88,12 @@ class DummyMailHandler < MailDiode::MailHandler
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
class RejectAllHandler < MailDiode::MailHandler
|
92
|
+
def valid_recipient?(to_address)
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
91
97
|
describe MailDiode::Engine do
|
92
98
|
before(:each) do
|
93
99
|
MailDiode::set_log_level(Logger::WARN)
|
@@ -323,6 +329,14 @@ describe MailDiode::Engine do
|
|
323
329
|
@engine.process_line("rcpt To:#{@valid_recipient}").should == MailDiode::RESULT_OK
|
324
330
|
@engine.process_line("rcpt To:#{@valid_recipient}").should == MailDiode::SMTPError::TOO_MANY_RECIPIENTS
|
325
331
|
end
|
332
|
+
|
333
|
+
it "should fail if there are too many rejected recipients" do
|
334
|
+
@engine.set_mail_handler(RejectAllHandler.new)
|
335
|
+
@engine.process_line("rcpt To:#{@valid_recipient}").index(MailDiode::SMTPError::UNKNOWN_RECIPIENT).should == 0
|
336
|
+
@engine.process_line("rcpt To:#{@valid_recipient}").should == MailDiode::SMTPError::TOO_MANY_REJECTS
|
337
|
+
@engine.terminate?.should == true
|
338
|
+
end
|
339
|
+
|
326
340
|
end
|
327
341
|
|
328
342
|
describe "DATA before RCPT" do
|
@@ -361,6 +375,15 @@ describe MailDiode::Engine do
|
|
361
375
|
@engine.process_line('.')
|
362
376
|
@engine.process_line('data').should == MailDiode::SMTPError::NEED_RCPT_BEFORE_DATA
|
363
377
|
end
|
378
|
+
|
379
|
+
it "should give error for message too long" do
|
380
|
+
@engine.max_data_lines = 10
|
381
|
+
@engine.process_line('data').should == MailDiode::RESULT_DATA_OK
|
382
|
+
@engine.max_data_lines.times do |n|
|
383
|
+
@engine.process_line('#n').should == nil
|
384
|
+
end
|
385
|
+
@engine.process_line('#n').should == MailDiode::SMTPError::MESSAGE_TOO_LONG
|
386
|
+
end
|
364
387
|
end
|
365
388
|
end
|
366
389
|
|
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.5
|
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-05-03 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|