class EventMachine::EmailServer::SMTPServer

Attributes

debug[RW]

Public Class Methods

classifier(classifier=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 68
def self.classifier(classifier=nil)
  if not classifier.nil?
    @@classifier = classifier
  end
  @@classifier
end
dnsbl_check(dnsbl_check=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 40
def self.dnsbl_check(dnsbl_check=nil)
  if not dnsbl_check.nil?
    @@dnsbl_check = dnsbl_check
  end
  @@dnsbl_check
end
graylist(graylist=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 33
def self.graylist(graylist=nil)
  if graylist
    @@graylist = graylist
  end
  @@graylist
end
new(hostname, userstore, emailstore) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 77
def initialize(hostname, userstore, emailstore)
  @hostname = hostname
  @userstore = userstore
  @emailstore = emailstore
  @debug = false
  @data_mode = false
  @email_body = ""
  @ptr_ok = true
  @dnsbl_ok = true
  @rate_ok = true
  @gray_ok = true
  @spf_ok = true
  @reject_ok = true
  @classifier_ok = true
  @pending_checks = [:content]
end
ratelimiter(ratelimiter=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 47
def self.ratelimiter(ratelimiter=nil)
  if ratelimiter
    @@ratelimiter = ratelimiter
  end
  @@ratelimiter
end
reject_filters(filters=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 54
def self.reject_filters(filters=nil)
  if filters
    @@reject_filters = filters
  end
  @@reject_filters
end
reset() click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 16
def self.reset
  @@graylist = nil
  @@dnsbl_check = nil
  @@ratelimiter = nil
  @@reverse_ptr_check = false
  @@spf_check = false
  @@reject_filters = Array.new
  @@classifier = nil
end
reverse_ptr_check(ptr=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 26
def self.reverse_ptr_check(ptr=nil)
  if not ptr.nil?
    @@reverse_ptr_check = ptr
  end
  @@reverse_ptr_check
end
spf_check(spf=nil) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 61
def self.spf_check(spf=nil)
  if not spf.nil?
    @@spf_check = spf
  end
  @@spf_check
end

Public Instance Methods

check_classifier() click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 158
def check_classifier
  if @@classifier
    if @@classifier.block?(@email_body)
      @classifier_ok = false
    end
  end
end
check_dnsbl(ip) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 122
def check_dnsbl(ip)
  if @@dnsbl_check
    @dnsbl_ok = false
    @pending_checks << :dnsbl
    EventMachine::DNSBL::Client.check(ip) do |results|
      @dnsbl_ok = ! EventMachine::DNSBL::Client.blacklisted?(results)
      @pending_checks -= [:dnsbl]
      if @pending_checks.length == 0
        send_answer
      end
    end
  end
end
check_gray(ip) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 142
def check_gray(ip)
  if @@graylist
    @gray_ok = @@graylist.has_key?(ip)
    @@graylist[ip] = true
  end
end
check_ptr(helo, ip) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 105
def check_ptr(helo, ip)
  if helo =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ and helo == ip
    @ptr_ok = true
  elsif @@reverse_ptr_check
    @ptr_ok = false
    @pending_checks << :ptr
    d = EM::DNS::Resolver.resolve helo
    d.callback { |r|
      @ptr_ok = r.include?(ip)
      @pending_checks -= [:ptr]
      if @pending_checks.length == 0
        send_answer
      end
    }
  end
end
check_ratelimit(ip) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 136
def check_ratelimit(ip)
  if @@ratelimiter
    @rate_ok = @@ratelimiter.use(ip)
  end
end
check_reject() click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 149
def check_reject
  @@reject_filters.each do |filter|
    if filter.match(@email_body)
      @reject_ok = false
      return
    end
  end
end
check_spf(helo, client_ip, identity) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 166
def check_spf(helo, client_ip, identity)
  if @@spf_check
    @spf_ok = false
    @pending_checks << :spf
    
    spf_dispatcher = EventMachine::ThreadedResource.new do
      spf_server = SPF::Server.new
    end

    pool = EM::Pool.new

    pool.add spf_dispatcher

    pool.perform do |dispatcher|
      completion = dispatcher.dispatch do |spf_server|
        if helo =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ and helo == ip
          helo = nil
        end
        request = SPF::Request.new(
          versions:      [1, 2],             # optional
          scope:         'mfrom',            # or 'helo', 'pra'
          identity:      identity,
          ip_address:    client_ip,
          helo_identity: helo   # optional
        )

        result = spf_server.process(request)
      end
  
      completion.callback do |result|
        if result.code == :pass
          @spf_ok = true
        elsif result.code == :fail
          @spf_ok = false
        elsif result.code == :softfail
          @spf_ok = true
        elsif result.code == :neutral
          @spf_ok = true
        else
          @spf_ok = false
        end
        @pending_checks -= [:spf]
        if @pending_checks.length == 0
          send_answer
        end
      end
  
      completion
    end          
  end
end
post_init() click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 94
def post_init
  send "220 #{@hostname} ESMTP Service ready"
end
process_line(line) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 242
def process_line(line)
        if (@data_mode) && (line.chomp == '.')
                @data_mode = false
  check_reject
  check_classifier
  @pending_checks -= [:content]
  p @pending_checks if @debug
  if @pending_checks.length == 0
    send_answer
  end
        elsif @data_mode
                @email_body += line
        elsif (line =~ /^(HELO|EHLO) (.*)/)
  helo = $2.chomp.gsub(/^\[/,'').gsub(/\]$/,'')
  port, ip = Socket.unpack_sockaddr_in(get_peername)
  check_ptr(helo, ip)
  check_dnsbl(ip)
  check_gray(ip)
  check_ratelimit(ip)
  send("250 hello #{ip} (#{helo})")
        elsif (line =~ /^QUIT/)
  send("221 #{@hostname} ESMTP server closing connection")
  self.close_connection
        elsif (line =~ /^MAIL FROM\:/)
                @mail_from = (/^MAIL FROM\:\s*<(.+)>.*$/).match(line)[1]
  if @@spf_check
    port, ip = Socket.unpack_sockaddr_in(get_peername)
    check_spf(helo, ip, @mail_from)
  end
  send("250 OK")
        elsif (line =~ /^RCPT TO\:/)
                rcpt_to = (/^RCPT TO\:\s*<(.+)>.*$/).match(line)[1]
                if @userstore.user_by_emailaddress(rcpt_to.strip)
                        @rcpt_to = rcpt_to
    send("250 OK")
  else
    send("550 No such user here")
  end
        elsif (line =~ /^DATA/)
                if @rcpt_to
                        @data_mode = true
                        @email_body = ''
                        send("354 Enter message, ending with \".\" on a line by itself")
  else
    send("500 ERROR")
  end
        else
                send("500 ERROR")
        end
end
receive_data(data) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 98
                  def receive_data(data)
  puts ">> #{data}" if @debug
  data.split(/\n/).each do |line|
    process_line(line+"\n")
  end
end
save() click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 293
def save
        begin
                subject = @email_body.match(/Subject\:\s*(.*?)[\r\n]/i)[1]
                u = @userstore.user_by_emailaddress(@rcpt_to.strip)
        rescue Exception => err
                puts err if @debug
                return
        end
        if u and @mail_from and @rcpt_to
                subject ||= ''
  @emailstore << Email.new(nil, @mail_from, @rcpt_to, subject, @email_body, u.id)
        end
end
send(msg) click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 237
def send(msg)
  puts "<< #{msg}" if @debug
  send_data("#{msg}\r\n")
end
send_answer() click to toggle source
# File lib/eventmachine/email_server/smtp_server.rb, line 218
def send_answer
  if @debug
    puts "ptr_ok        = #{@ptr_ok}"
    puts "rate_ok       = #{@rate_ok}"
    puts "gray_ok       = #{@gray_ok}"
    puts "reject_ok     = #{@reject_ok}"
    puts "dnsbl_ok      = #{@dnsbl_ok}"
    puts "spf_ok        = #{@spf_ok}"
    puts "classifier_ok = #{@classifier_ok}"
  end
  if @ptr_ok and @rate_ok and @gray_ok and @reject_ok and @dnsbl_ok and @spf_ok and @classifier_ok
    ans = "250 OK"
    save
  else
    ans = "451 Requested action aborted: local error in processing"
  end
  send(ans)
end