#!/bin/sh exec ruby -x -S $0 "$@" #!/usr/bin/ruby require "ftools" require "net/smtp" require "net/pop" module MailClient Version = "0.3" POP = [] BIND = binding def module_load file open(File.expand_path file) do |rc| eval rc.read, BIND raise $! if $! end end module_function :module_load module_load "~/.mailclient.rb" if defined? SPOOL_DIR SPOOL = File.expand_path(SPOOL_DIR) else SPOOL = File.expand_path("~/spool/mqueue") end File.makedirs SPOOL if defined? DONT_DELETE and DONT_DELETE DELETE = false elsif defined? DONT_DELETE_IMMEDIATELY and DONT_DELETE_IMMEDIATELY DELETE = :delete else DELETE = :delete! end class MailRead attr_reader :header, :body def initialize f #Fix me @header = f.gets("\n\n") @body = f.read || '' end def remove_bcc @header.gsub!(/^bcc:.*\n/i, '') end def field x # /^#{x}:[ \t]*(.*?)\n/i === @header /^#{x}:(.*?\n(?:[ \t]+.*?\n)*)/i === @header $1 end def method_missing x field x.id2name end #Fix me def MailRead.extract addresses addresses.gsub(/\(.*?\)/, '').gsub(/"(.*),(.*)"/, "\"\\1\1\\2\""). split(/,\s*/).collect do |address| address.gsub!(/\1/, ',') address = $1 if /<(.+?)>/ === address address.strip end end end class Sendmail 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", @contents end def send smtp unless @recipients.empty? smtp.sendmail @contents, @sender, @recipients end end def deliver user Sendmail.deliver @contents, user end def Sendmail.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 Sendmail.parse stream, recipients = nil mail = MailRead.new(stream) if mail.From sender = MailRead.extract(mail.From)[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.Bcc] recipients += MailRead.extract(addr) if addr end mail.remove_bcc end contents = '' time = Time.now if not mail.field("Date") contents.concat time.strftime("Date: %a, %d %h %Y %X +0900\n") end if not mail.field("Message-Id") contents.concat time.strftime("Message-Id: <%Y%m%d%H%M%s.QAA") + format("%05d", Process.pid) + "@" + LOCAL_DOMAIN_FQDN + ">\n" end contents.concat mail.header.sub(/(\r\n|\r|\n){2}\z/) { $1 + format("X-Dispatcher: mailclient-%s + maillib-%s (ruby %s [%s])", MailClient::Version, Net::Session::Version, RUBY_VERSION, RUBY_PLATFORM) + $1 + $1 } contents.concat mail.body return Sendmail.new contents, sender, recipients end def Sendmail.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 = Net::SMTPSession.new(SMTP_SERVER) smtp.start LOCAL_DOMAIN_FQDN begin mail.send smtp ensure smtp.finish end end def generate_filename filename = File.expand_path Time.new.strftime("%Y%m%d%H%M-000"), SPOOL while File.exist? 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 stream = STDIN extract, recipients = parseopts if extract mail = Sendmail.parse(stream) else mail = Sendmail.parse(stream, 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 = Net::SMTPSession.new(SMTP_SERVER) smtp.start LOCAL_DOMAIN_FQDN begin for file in files if File.file? file open(file) do |f| mail = Sendmail.load(f) mail.send smtp end File.delete file end end ensure smtp.finish end end end def fetchmail for popconf in POP if popconf[:TYPE] == :APOP client_class = Net::APOPSession else client_class = Net::POP3Session end pop = client_class.new(popconf[:SERVER]) pop.start(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): " Sendmail.deliver(mail.all.gsub!(/\r\n/, "\n"), ENV["USER"]) mail.send(DELETE) if DELETE puts "flushed" i += 1 end ensure pop.finish end end end end if __FILE__ == $0 include MailClient module_load MailClient::HOOK if defined? MailClient::HOOK STDOUT.sync = true if ARGV.include? "--pop" fetchmail elsif /^(sendmail|imput)/ =~ File.basename($0) or ARGV.include? "--sendmail" sendmail elsif ARGV.include? "-q" sendqueue else job = Thread.start { sendqueue } fetchmail job.join end end