class QuickML::Session

Constants

COMMAND_TABLE

Public Class Methods

new(config, logger, catalog, socket) click to toggle source

def initialize (config, socket)

# File vendor/qwik/lib/qwik/ml-session.rb, line 43
    def initialize (config, logger, catalog, socket)
      @socket = socket
      @config = config
#      @logger = @config.logger
#      @catalog = @config.catalog
      @logger = logger
      @catalog = catalog

      @hello_host = 'hello.host.invalid'
      @protocol = nil
      @peer_hostname = @socket.hostname
      @peer_address = @socket.address
      @remote_host = (@peer_hostname or @peer_address)

      @data_finished = false
      @my_hostname = 'localhost'
      @my_hostname = Socket.gethostname if @config.ml_port == 25
      @message_charset = nil
    end

Public Instance Methods

start() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 63
def start
  elapsed = calc_time {
    _start
  }
  @logger.vlog "Session finished: #{elapsed} sec."
end

Private Instance Methods

_start() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 79
def _start
  begin
    def_puts(@socket)
    connect
    timeout(@config.timeout) {
      process
    }
  rescue TimeoutError
    @logger.vlog "Timeout: #{@remote_host}"
  ensure
    close
  end
end
calc_time() { || ... } click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 72
def calc_time
  start_time = Time.now
  yield
  elapsed = Time.now - start_time
  return elapsed
end
cleanup_connection() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 257
def cleanup_connection
  unless @data_finished
    discard_data
  end
  @socket.puts '221 Bye'
  close
end
close() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 303
def close
  return if @socket.closed?
  @socket.close
  @logger.vlog "Closed: #{@remote_host}"
end
connect() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 104
def connect
  @socket.puts "220 #{@my_hostname} ESMTP QuickML"
  @logger.vlog "Connect: #{@remote_host}"
end
content_type() click to toggle source

FIXME: this is the same method of QuickML#content_type

# File vendor/qwik/lib/qwik/ml-session.rb, line 299
def content_type
  return Mail.content_type(@config.content_type, @message_charset)
end
data(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 203
def data (mail, arg)
  if mail.recipients.empty?
    @socket.puts '503 Error: need RCPT command'
  else
    @socket.puts '354 send the mail data, end with .';
    begin
      read_mail(mail)
    ensure
      @message_charset = mail.charset
    end
    @socket.puts '250 ok'
  end
end
def_puts(socket) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 93
def def_puts(socket)
  def socket.puts(*objs)
    objs.each {|x|
      begin
        self.print x.xchomp, "\r\n"
      rescue Errno::EPIPE
      end
    }
  end
end
discard_data() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 265
def discard_data
  begin
    while line = @socket.safe_gets
      break if end_of_data?(line)
    end
  rescue TooLongLine
    retry
  end
end
ehlo(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 153
def ehlo (mail, arg)
  @hello_host = arg.split.first
  @socket.puts "250-#{@my_hostname}"
  @socket.puts '250 PIPELINING'
  @protocol = 'ESMTP'
end
end_of_data?(line) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 240
def end_of_data? (line)
# line.xchomp == '.'
  return line == ".\r\n"
end
helo(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 146
def helo (mail, arg)
  return if arg.nil?
  @hello_host = arg.split.first
  @socket.puts "250 #{@my_hostname}"
  @protocol = 'SMTP'
end
mail(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 192
def mail (mail, arg)
  if @protocol.nil?
    @socket.puts '503 Error: send HELO/EHLO first'
  elsif /^From:\s*<(.*)>/i =~ arg or /^From:\s*(.*)/i =~ arg 
    mail.mail_from = $1
    @socket.puts '250 ok'
  else
    @socket.puts "501 Syntax: MAIL FROM: <address>"
  end
end
noop(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 160
def noop (mail, arg)
  @socket.puts '250 ok'
end
process() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 109
def process
  until @socket.closed?
    begin
      mail = Mail.new
      receive_mail(mail)
      if mail.valid?
        processor = Processor.new(@config, mail)
        processor.process
      end
    rescue TooLargeMail
      cleanup_connection
      report_too_large_mail(mail) if mail.valid?
      @logger.log "Too Large Mail: #{mail.from}"
    rescue TooLongLine
      cleanup_connection
      @logger.log "Too Long Line: #{mail.from}"
    end
  end
end
quit(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 164
def quit (mail, arg)
  @socket.puts '221 Bye'
  close
end
rcpt(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 175
def rcpt (mail, arg)
  if mail.mail_from.nil?
    @socket.puts '503 Error: need MAIL command'
  elsif /^To:\s*<(.*)>/i =~ arg or /^To:\s*(.*)/i =~ arg
    address = $1
    if Mail.address_of_domain?(address, @config.ml_domain)
      mail.add_recipient(address)
      @socket.puts '250 ok'
    else
      @socket.puts "554 <#{address}>: Recipient address rejected"
      @logger.vlog "Unacceptable RCPT TO:<#{address}>"
    end
  else
    @socket.puts "501 Syntax: RCPT TO: <address>"
  end
end
read_mail(mail) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 217
def read_mail (mail)
  len = 0
  lines = []
  while line = @socket.safe_gets
    break if end_of_data?(line)
    len += line.length
    if @config.max_mail_length < len
      mail.read(lines.join('')) # Generate a header for an error report.
      raise TooLargeMail 
    end
    line.sub!(/^\.\./, '.') # unescape
    line = line.normalize_eol
    lines << line
    # I do not know why but constructing mail_string with
    # String#<< here is very slow.
    # mail_string << line
  end
  mail_string = lines.join('')
  @data_finished = true
  mail.read(mail_string)
  mail.unshift_field('Received', received_field)
end
receive_mail(mail) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 129
def receive_mail (mail)
  while line = @socket.safe_gets
    line = line.xchomp
    command, arg = line.split(/\s+/, 2)
    return if command.nil? || command.empty?
    command = command.downcase.intern  # 'HELO' => :helo
    if COMMAND_TABLE.include?(command)
      @logger.vlog "Command: #{line}"
      send(command, mail, arg)
    else
      @logger.vlog "Unknown SMTP Command: #{command} #{arg}"
      @socket.puts '502 Error: command not implemented'
    end
    break if command == :quit or command == :data
  end
end
received_field() click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 245
def received_field
  sprintf("from %s (%s [%s])\n" + 
          "        by %s (QuickML) with %s;\n" + 
          "        %s", 
          @hello_host,
          @peer_hostname, 
          @peer_address,
          @my_hostname,
          @protocol,
          Time.now.rfc2822)
end
report_too_large_mail(mail) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 275
def report_too_large_mail (mail)
  header = []
  subject = Mail.encode_field(_("[QuickML] Error: %s", mail['Subject']))
  header.push(['To',        mail.from],
              ['From',    @config.ml_postmaster],
              ['Subject', subject],
              ['Content-Type', content_type])

  max  = @config.max_mail_length.commify
  body =   _("Sorry, your mail exceeds the length limitation.\n")
  body <<  _("The max length is %s bytes.\n\n", max)
  orig_subject = codeconv(Mail.decode_subject(mail['Subject']))
  body << "Subject: #{orig_subject}\n"
  body << "To: #{mail['To']}\n"
  body << "From: #{mail['From']}\n"
  body << "Date: #{mail['Date']}\n"
  Sendmail.send_mail(@config.smtp_host, @config.smtp_port, @logger,
                 :mail_from => '', 
                 :recipient => mail.from,
                 :header => header,
                 :body => body)
end
rset(mail, arg) click to toggle source
# File vendor/qwik/lib/qwik/ml-session.rb, line 169
def rset (mail, arg)
  mail.mail_from = nil
  mail.clear_recipients
  @socket.puts '250 ok'
end