#!/usr/bin/env ruby

require 'etc'
require 'socket'
require 'thread'
require 'timeout'

hostname = `hostname -f`.strip

def prefix
  case Thread.current[:type]
    when :server then
      type = "S"
      addr = Thread.current[:self]
    when :client then
      type = "C"
      addr = Thread.current[:peer]
    else
      type = "-"
      addr = nil
  end
  ident = addr ? sprintf("%s:%s(%s)", type, addr[3], addr[1]) : type
  sprintf("%s %s", Time.now.strftime("%Y-%m-%dT%H:%M:%S@%Z"), ident)
end

$stdout_mutex = Mutex.new
def mputs(str)
  $stdout_mutex.synchronize {
    STDOUT.printf("%s: ", prefix)
    STDOUT.printf("%s\n", str)
    STDOUT.flush
  }
end
def mprintf(*args)
  $stdout_mutex.synchronize {
    STDOUT.printf("%s: ", prefix)
    STDOUT.printf(*args)
    STDOUT.flush
  }
end

def clientf(*args)
  return false unless Thread.current[:type] == :client
  Thread.current[:sock].printf(*args)
  Thread.current[:sock].flush
end

TCPServer.open(25) {|smtpd|
  Thread.current[:type] = :server
  Thread.current[:sock] = smtpd
  Thread.current[:self] = smtpd.addr

  # Drop root privs
  nobody = Etc.getpwnam('nobody')
  Process::Sys.setgid(nobody.gid)
  Process::Sys.setuid(nobody.uid)

  mputs("ready for service")
  loop {
    Thread.start(smtpd.accept) {|client|
      Thread.current[:type] = :client
      Thread.current[:sock] = client
      Thread.current[:peer] = client.peeraddr
      mputs("accepted")
      mprintf("is %s\n", client.peeraddr[2]) unless client.peeraddr[2] == client.peeraddr[3]
      begin
        mputs("enter loop")
        Timeout.timeout(60) {
          clientf("220 %s rejectelator 0.0\n", hostname)
          while input = client.gets
            command, args = input.strip.split(' ', 2)
            case command
              when /^HELO$/i then
                mprintf("> helo %s\n", args.inspect)
                clientf("250 Hello %s, nicetameetcha\n", args)
              when /^MAIL$/i then
                mprintf("> mail %s\n", args.inspect)
                clientf("250 Ok\n")
              when /^RCPT$/i then
                mprintf("> rcpt %s\n", args.inspect)
                clientf("550 I will not accept any mail ever; you might as well give up now\n")
              when /^DATA$/i then
                mprintf("> data %s\n", args.inspect)
                clientf("550 Need RCPT first\n")
              when /^QUIT$/i then
                mprintf("> quit %s\n", args.inspect)
                break
              else
                mprintf("Bad cmd %s (%s)\n", command.inspect, args.inspect)
                clientf("550 Unrecognized command\n")
            end
          end
        }
      rescue Exception => err
        case err
          when Timeout::Error then
            mputs("timed out")
          else
            mprintf("Exception: %s: %s\n%s\n\n", err.class, err.message, err.backtrace.join("\n"))
        end
      ensure
        clientf("221 Bye\n")
        client.close
        mputs("closed")
      end
    }
  }
}