#!/usr/bin/ruby require "ftools" require "rfc822" require "smtpclient" require "popclient" MAILCLIENT_VERSION = "0.1" POP3 = :POP3 APOP = :APOP POP = [] STDOUT.sync = true class RFC822 attr_reader :header_string end load(File.expand_path("~/.mailclient.rb")) if defined?(SPOOL_DIR) SPOOL = File.expand_path(SPOOL_DIR) else SPOOL = File.expand_path("~/spool/mqueue") end unless File.exist?(SPOOL) File.makedirs(SPOOL) end if defined?(DONT_DELETE) && DONT_DELETE DELETE = false elsif defined?(DONT_DELETE_IMMEDIATELY) && DONT_DELETE_IMMEDIATELY DELETE = :delete else DELETE = :delete! end class Mail MDA_FORMAT = "|" + MDA attr_reader :contents, :sender, :recipients def initialize(contents, sender, recipients) @contents = contents @sender = sender @recipients = recipients end def save(file) file.chmod(0600) file.print("<", @sender, "\n") for recipient in @recipients file.print(">", recipient, "\n") end file.print("\n") file.print(@contents) end def send(smtp) unless @recipients.empty? smtp.sendmail(@contents, @sender, @recipients) end end def deliver(user) Mail.deliver(@contents, user) end def Mail.load(file) sender = file.gets[1..-2] recipients = [] while /^$/ !~ line = file.gets recipients.push(line[1..-2]) end contents = file.read return new(contents, sender, recipients) end def Mail.parse(file, recipients = nil) mail = RFC822.new(STDIN) if mail.from sender = mail.from.get_address[0] elsif POP[0] sender = POP[0][:USER] + "@" + POP[0][:SERVER] else raise "can't get sender address" end if recipients.nil? recipients = [] for addr in [mail.to, mail.cc, mail.get_address("Bcc")] recipients += addr.get_address if addr end end contents = mail.header_string.gsub(/(\r\n|\r|\n){2}\Z/) { $1 + format("X-Dispatcher: mailclient version %s (ruby %s)", MAILCLIENT_VERSION, VERSION) + $1 + $1 } contents.concat(mail.body) return Mail.new(contents, sender, recipients) end def Mail.deliver(mail, user) open(format(MDA_FORMAT, user), "w") do |dma| dma.write(mail) end end end def parseopts extract = false recipients = [] if File.basename($0) =~ /^imput/ extract = true else while arg = ARGV.shift if /^-/ =~ arg case arg[1..-1] when "t" extract = true when /^(C|oC|F|f|h|oA|oD|oE|oG|oL|oQ|oR|oT|oX|oM?)$/ # ignore option ARGV.shift else # ignore option end else recipients.push(arg) end end end return extract, recipients end def smtpsend(mail) smtp = SMTPclient.new(SMTP_SERVER, 25, nil, SMTP_TIMEOUT, SMTP_TIMEOUT) smtp.login begin mail.send(smtp) ensure smtp.logout end end def generate_filename filename = File.expand_path(Time.new.strftime("%Y%m%d%H%M-000"), SPOOL) while File.exist?(filename) filename = filename.succ end return filename end def savemail(mail) filename = generate_filename file = open(filename, "w") begin begin mail.save(file) ensure file.close end rescue Exception File.delete(filename) if File.exist?(filename) raise end end def sendmail extract, recipients = parseopts if extract mail = Mail.parse(STDIN) else mail = Mail.parse(STDIN, $recipients) end # mail.recipients.delete_if do |recipient| # if /@/ !~ recipient # mail.deliver(recipient) # true # else # false # end # end # return if mail.recipients.empty? # begin # smtpsend(mail) # rescue savemail(mail) # end end def sendqueue files = Dir[File.expand_path("*", SPOOL)].find_all { |i| /\/\d{12}-\d{3,}$/ =~ i } unless files.empty? puts "sending mails to #{SMTP_SERVER}..." smtp = SMTPclient.new(SMTP_SERVER) smtp.login begin for file in files if File.file?(file) open(file) do |f| mail = Mail.load(f) mail.send(smtp) end File.delete(file) end end ensure smtp.logout end end end def fetchmail for popconf in POP if popconf[:TYPE] == APOP client_class = APOPclient else client_class = POP3client end pop = client_class.new(popconf[:SERVER],110, nil, nil, nil, POP3_TIMEOUT, POP3_TIMEOUT) pop.login(popconf[:USER], popconf[:PASSWORD]) begin count = pop.mails.size puts "#{count} message for #{popconf[:USER]} at #{popconf[:SERVER]}." i = 1 for mail in pop.mails print "reading message #{i} of #{count} (#{mail.size} bytes): " Mail.deliver(mail.mail.gsub!(/\r\n/, "\n"), ENV["USER"]) mail.send(DELETE) if DELETE puts "flushed" i += 1 end ensure pop.logout end end end if /^(sendmail|imput)/ =~ File.basename($0) || ARGV.delete("--sendmail") sendmail sendqueue else sendqueue fetchmail end