Class: MailDiode::Engine
- Inherits:
-
Object
- Object
- MailDiode::Engine
- Defined in:
- lib/engine.rb
Overview
State machine that handles incoming SMTP commands
Constant Summary collapse
- MAX_ALLOWED_REJECTS =
1
Instance Attribute Summary collapse
-
#envelope ⇒ Object
readonly
Returns the value of attribute envelope.
-
#max_data_lines ⇒ Object
Returns the value of attribute max_data_lines.
Instance Method Summary collapse
- #add_filter(filter) ⇒ Object
- #apply_filters(sender, recipient) ⇒ Object
- #do_data(args) ⇒ Object
- #do_helo(args) ⇒ Object
- #do_mail(args) ⇒ Object
- #do_message_text(line) ⇒ Object
- #do_noop(args) ⇒ Object
- #do_quit(args) ⇒ Object
- #do_rcpt(args) ⇒ Object
- #do_rset(args) ⇒ Object
- #do_vrfy(args) ⇒ Object
- #enforce_address_arg(args, keyword, error_text) ⇒ Object
- #enforce_host_arg(args, error_text) ⇒ Object
- #enforce_no_args(args, error_text) ⇒ Object
- #extract_args(line) ⇒ Object
-
#initialize(hostname, max_recipients) ⇒ Engine
constructor
A new instance of Engine.
- #process_command(command, args) ⇒ Object
- #process_line(line) ⇒ Object
- #process_message(sender, recipient, text) ⇒ Object
- #raise_smtp_error(text) ⇒ Object
- #set_mail_handler(handler) ⇒ Object
- #start(sender_ip) ⇒ Object
- #terminate? ⇒ Boolean
- #terminate_message ⇒ Object
Constructor Details
#initialize(hostname, max_recipients) ⇒ Engine
Returns a new instance of Engine.
90 91 92 93 94 95 96 97 98 |
# File 'lib/engine.rb', line 90 def initialize(hostname, max_recipients) @hostname = hostname @greeting = "220 #{hostname} ESMTP" @helo_response = "250 #{hostname}" @filters = [] @max_recipients = max_recipients @rejected_count = 0 @max_data_lines = 1000000 end |
Instance Attribute Details
#envelope ⇒ Object (readonly)
Returns the value of attribute envelope.
85 86 87 |
# File 'lib/engine.rb', line 85 def envelope @envelope end |
#max_data_lines ⇒ Object
Returns the value of attribute max_data_lines.
86 87 88 |
# File 'lib/engine.rb', line 86 def max_data_lines @max_data_lines end |
Instance Method Details
#add_filter(filter) ⇒ Object
104 105 106 |
# File 'lib/engine.rb', line 104 def add_filter(filter) @filters << filter end |
#apply_filters(sender, recipient) ⇒ Object
235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/engine.rb', line 235 def apply_filters(sender, recipient) filterable_data = FilterableData.new filterable_data.sender_ip = @sender_ip filterable_data.helo = @helo filterable_data.sender = sender filterable_data.recipient = recipient filterable_data.original_recipient = recipient @filters.each do | filter | if filter.respond_to? :process filter.process(filterable_data) end end return filterable_data end |
#do_data(args) ⇒ Object
222 223 224 225 226 227 228 229 |
# File 'lib/engine.rb', line 222 def do_data(args) if(! @envelope || ! @envelope.sender) raise_smtp_error(SMTPError::NEED_RCPT_BEFORE_DATA) end enforce_no_args(args, SMTPError::SYNTAX_DATA) @message_text = [] return RESULT_DATA_OK end |
#do_helo(args) ⇒ Object
164 165 166 167 168 169 170 171 172 173 |
# File 'lib/engine.rb', line 164 def do_helo(args) enforce_host_arg(args, SMTPError::SYNTAX_HELO) @helo = args @filters.each do | filter | if filter.respond_to? :helo filter.helo @helo end end return @helo_response end |
#do_mail(args) ⇒ Object
186 187 188 189 190 191 192 193 194 195 |
# File 'lib/engine.rb', line 186 def do_mail(args) from_address = enforce_address_arg(args, 'from', SMTPError::SYNTAX_MAIL) @envelope = Envelope.new(from_address) @filters.each do | filter | if filter.respond_to? :mail filter.mail from_address end end return RESULT_OK end |
#do_message_text(line) ⇒ Object
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/engine.rb', line 250 def (line) if line == '.' return end if line[0..0] == '.' line = line[1..-1] end if @message_text.size >= max_data_lines raise_smtp_error(SMTPError::MESSAGE_TOO_LONG) end @message_text << line return nil end |
#do_noop(args) ⇒ Object
153 154 155 156 |
# File 'lib/engine.rb', line 153 def do_noop(args) enforce_no_args(args, SMTPError::SYNTAX_NOOP) return RESULT_OK end |
#do_quit(args) ⇒ Object
158 159 160 161 162 |
# File 'lib/engine.rb', line 158 def do_quit(args) enforce_no_args(args, SMTPError::SYNTAX_QUIT) @should_terminate = true return RESULT_BYE end |
#do_rcpt(args) ⇒ Object
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/engine.rb', line 197 def do_rcpt(args) if(! @envelope) raise_smtp_error(SMTPError::NEED_MAIL_BEFORE_RCPT) end if(@envelope.recipients.size >= @max_recipients) raise_smtp_error(SMTPError::TOO_MANY_RECIPIENTS) end recipient = enforce_address_arg(args, 'to', SMTPError::SYNTAX_RCPT) @filters.each do | filter | if filter.respond_to? :rcpt filter.rcpt recipient end end filterable_data = apply_filters(@envelope.sender, recipient) if !@mail_handler.valid_recipient?(filterable_data.recipient) if @rejected_count >= MAX_ALLOWED_REJECTS raise_smtp_error(SMTPError::TOO_MANY_REJECTS) end @rejected_count += 1 raise_smtp_error(SMTPError::UNKNOWN_RECIPIENT + ": " + recipient) end @envelope.add_recipient(recipient) return RESULT_OK end |
#do_rset(args) ⇒ Object
180 181 182 183 184 |
# File 'lib/engine.rb', line 180 def do_rset(args) enforce_no_args(args, SMTPError::SYNTAX_RSET) @envelope = nil return RESULT_OK end |
#do_vrfy(args) ⇒ Object
175 176 177 178 |
# File 'lib/engine.rb', line 175 def do_vrfy(args) enforce_host_arg(args, SMTPError::SYNTAX_VRFY) return RESULT_UNSURE end |
#enforce_address_arg(args, keyword, error_text) ⇒ Object
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/engine.rb', line 305 def enforce_address_arg(args, keyword, error_text) re = /#{keyword}:\s*(.+)/i match = re.match(args) if !match raise_smtp_error(error_text) end candidate = match[1].strip strip_brackets = /<(.*)>/ match = strip_brackets.match(candidate) if(match) candidate = match[1] end return candidate end |
#enforce_host_arg(args, error_text) ⇒ Object
298 299 300 301 302 303 |
# File 'lib/engine.rb', line 298 def enforce_host_arg(args, error_text) args = args.split(/\s+/) if args.size != 1 raise_smtp_error(error_text) end end |
#enforce_no_args(args, error_text) ⇒ Object
292 293 294 295 296 |
# File 'lib/engine.rb', line 292 def enforce_no_args(args, error_text) if ! args.empty? raise_smtp_error(error_text) end end |
#extract_args(line) ⇒ Object
321 322 323 324 325 326 327 328 329 330 |
# File 'lib/engine.rb', line 321 def extract_args(line) command, args = line.split(nil, 2) if command.nil? command = '' end if args.nil? args = '' end return command, args end |
#process_command(command, args) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/engine.rb', line 137 def process_command(command, args) case command.upcase when NOOP then return do_noop(args) when QUIT then return do_quit(args) when HELO then return do_helo(args) when EHLO then return do_helo(args) when VRFY then return do_vrfy(args) when RSET then return do_rset(args) when MAIL then return do_mail(args) when RCPT then return do_rcpt(args) when DATA then return do_data(args) end raise_smtp_error(SMTPError::BAD_COMMAND + ': ' + command) end |
#process_line(line) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/engine.rb', line 113 def process_line(line) begin if @message_text return (line) end command, args = extract_args(line) result = process_command(command, args) MailDiode::log_success(command, args, result) return result rescue SMTPError => error MailDiode.log_info(error.text) if(error.text.index(SMTPError::TOO_MANY_REJECTS) == 0) @should_terminate = true end return error.text end end |
#process_message(sender, recipient, text) ⇒ Object
280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/engine.rb', line 280 def (sender, recipient, text) received = "Received: from #{@helo} (#{@sender_ip}) " + "by #{@hostname} " + "for <#{recipient}> " + "at #{Time.now.to_s}" full_text = received + NEWLINE + text.join(NEWLINE) filterable_data = apply_filters(sender, recipient) id = @mail_handler.(recipient, filterable_data.recipient, full_text) return id end |
#raise_smtp_error(text) ⇒ Object
332 333 334 |
# File 'lib/engine.rb', line 332 def raise_smtp_error(text) raise SMTPError.new(text) end |
#set_mail_handler(handler) ⇒ Object
100 101 102 |
# File 'lib/engine.rb', line 100 def set_mail_handler(handler) @mail_handler = handler end |
#start(sender_ip) ⇒ Object
108 109 110 111 |
# File 'lib/engine.rb', line 108 def start(sender_ip) @sender_ip = sender_ip return @greeting end |
#terminate? ⇒ Boolean
133 134 135 |
# File 'lib/engine.rb', line 133 def terminate? return @should_terminate end |
#terminate_message ⇒ Object
266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/engine.rb', line 266 def sender = @envelope.sender recipients = @envelope.recipients text = @message_text @envelope = nil @message_text = nil ids = [] recipients.each do | recipient | ids << (sender, recipient, text) end return "#{RESULT_OK} #{ids.join(';')}" end |