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