maildiode 0.2.3 → 0.2.5

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