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.
@@ -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
- result = process_command(command, args)
116
- MailDiode::log_success(command, args, result)
117
- return result
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::UNKNOWN_RECIPIENT + ": " + recipient)
33
+ raise MailDiode::SMTPError.new(MailDiode::SMTPError::TOO_MANY_REJECTS + ": " + recipient)
34
34
  end
35
35
  end
36
36
  end
@@ -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
- line = get_line(client, TIMEOUT_SECONDS)
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
- MailDiode::log_debug "Client disconnected"
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::log_debug "Client reset connection"
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
- log_new_connection(client)
79
+ MailDiode::log_debug("##{client_id(client)}: connecting #{client.peeraddr[2]} -> #{client.peeraddr[3]}")
74
80
  true
75
81
  end
76
82
 
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})")
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(TIMEOUT_SECONDS)
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
- TIMEOUT_SECONDS = 1200 # djb recommends 1200
128
+ DEFAULT_TIMEOUT_SECONDS = 120 # djb recommends 1200
123
129
  end
124
130
  end
@@ -6,6 +6,7 @@ server ip 0.0.0.0
6
6
  server port 10025
7
7
  server maxconnections 20
8
8
  server maxrecipients 5
9
+ server timeoutseconds 120
9
10
  #server user maildiode
10
11
 
11
12
  # Define some maildirs: name and location
@@ -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.3
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-04-05 00:00:00 -04:00
12
+ date: 2009-05-03 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency