#!/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
}
}
}