-----BEGIN PGP SIGNED MESSAGE-----

In article <anmco6$ffo3l$1 / ID-57631.news.dfncis.de>,
Stefan Schmiedl  <s / xss.de> wrote:
>On Sat, 5 Oct 2002 16:14:53 +0900,
>Pe, Botp <botp / delmonte-phil.com> wrote:
>> 
>> 
>>> Stefan Schmiedl [mailto:s / xss.de] explains
>>>
>>>> >> Julian Fitzell [mailto:julian / beta4.com] enlightens
>>> >> 
>>> >> ...
>>> >> accept if message.subject =~ /Ruby Weekly News/i
>>> >> accept if message.subject =~ /ruby-dev summary/i
>>> >> ...
>>> >> 
>>> 
>>> It seems to be a handmade replacement of the
>>> fetchmail - procmail toolchain on linux.
>>> 
>> Yes. I've seen procmail scripts, and they don't come close...
>> 
>> I hope he'd share it though ;-)
>> 
>
>Well, it's not that difficult ... when I upgraded
>my box last week, the new version of fetchmail got
>confused by something in the first message of the
>mailbox, so I took a look at net/smtp.rb (IIRC),
>and fumbled something together to take a closer look.
>
>However, a simultaneously performed downgrade of
>fetchmail did the job, too, so I stopped tinkering.
>
>It is very easily doable, as long as your output
>is easy to manage, like appending to different files.
>
>Go on, do it yourself ... or investigate at RAA.
>
>s.


- - Here's mine. It's got a whole bunch of wierd stuff in it since
  I need to read my email out of NFS and write it into AFS. It
  uses ifile and the rmail and rfilter modules, but it might
  give you some ideas. I've found the rmail/rfilter stuff pretty
  useful. 

- - Booker C. Bense 

#!/var/local/bin/ruby
# Get the Mailspool and process it. 
#
#

require 'rmail/parser'
require 'rmail/parser/mbox' 
require 'tempfile'

require 'rfilter/deliver'

# Manage Ifile interactions

#
# $Id: ifile.rb,v 1.1 2002/09/25 20:02:13 bbense Exp $
#
# Ifile, a class for interacting with ifile program.
# Get ifile at http://www.ai.mit.edu/~jrennie/ifile/
#
# Booker C. Bense <bbense / slac.stanford.edu> 
#

require 'open3'

module Ifile

# This is the wrong name, but I can't think of anything better.
class Process
  # Tell me where ifile lives.
  def initialize(path="/var/local/bin/ifile",args="--verbosity=0") 
    if FileTest.executable?(path) then 
      @ifile = path
      @args = args
    else
      raise ArgumentError 
    end 
  end

  # Given a message, query folders

  def query(msg)
    results = Array.new
    output = self.run_ifile(msg,"--query ")
    i = 0 
    output.each do |line|
      # Format of output is folder score
      folder , score = line.split
      if ( folder && score ) then 
	tmp = Hash.new
	tmp['folder'] = folder
	tmp['score'] = score.to_f
	tmp['position'] = i 
	results << tmp 
	i = i + 1 
      end
    end 
    return results
  end 

  # Add a message to a folder
  def add(msg,folder)
    output = self.run_ifile(msg,"--insert=#{folder}") 
  end
  # Delete a message from a folder
  def delete(msg,folder) 
    output = self.run_ifile(msg,"--delete=#{folder}") 
  end 

  # Refile
  def refile(msg,oldfolder,newfolder) 
    self.delete(msg,oldfolder) 
    self.add(msg,newfolder)
  end 
# internal methods
  
  
  def run_ifile(msg,args) 
    stdin, stdout, stderr = Open3.popen3("#{@ifile} #{@args} #{args}")
    #write msg to ifile
    msg.each { |line| stdin.puts line } 
    stdin.close
    #Read output 
    output = stdout.readlines
    stdout.close
    stderr.close
    return output
  end 


end


end # module Ifile
 

class MailSpool 
  # Set the spool name
  def initialize(user,path="/var/spool/mail/")
    if ( user == nil ) then
    	user = Etc.getpwuid(Process.uid).name	
    end
    @spool = File.open("#{path}#{user}","r+");   
    @tmpspool = Tempfile.new("mailspool") 
    @parser = RMail::Parser.new
    @mreader = RMail::Parser::MBoxReader.new(@tmpspool)
  end
  # Move the spool to tmpspool 

  def update
    begin
      if @spool.stat.size > 0 then
	# Lock the spool file	
	@spool.flock(File::LOCK_EX) 
	# Read it in 
	@spool.rewind
	new_msgs = @spool.read
	# Empty it.
	@spool.truncate(0)
        # Unlock it 
	@spool.flock(File::LOCK_UN)
	@tmpspool.truncate(0)
	# Write it out to tmpspool
	@tmpspool.write(new_msgs)
        @tmpspool.close
      return true
     else
      return nil
     end 
    rescue 
      return nil
    end
  end

  # Return an array of messages. 
  def messages
    @tmpspool.open
  
    messages = []
    loop do
      msg = @parser.parse(@mreader.next)
      if msg.header.mbox_from then
	messages << msg
      else
	break
      end
    end
    messages
  end  

end 

# These are really HeaderFilters. 
class Filter
  
  # remember the regexp test and the folder it maps to. 
  def initialize ( test, folder ) 
    @test = test
    @folder = folder
    @has_proc = nil
  end 

  def test
    @test
  end 

  def folder
    @folder
  end

  def has_proc?
    @has_proc
  end
  
# This proc is for after a successful match to do 
# dynamic folder setting. 
  def addProc(proc)
    if defined? proc.call then 
      @proc = proc
      @has_proc = true
    else
      raise ArguementError
    end
  end 

  def call(msg) 
    @proc.call(msg)
  end

end 

# Return a string that indicates a folder to file into. 
# Add auto month splitting ? 
class FilterProcess 

 def initialize(user, filterdir, default_folder="incoming" )
   @user = user
   @default_folder = "#{filterdir}/#{default_folder}"
   @filterdir = filterdir

#   Switch to using date folder for auto month rollover
#   @folderdir = "#{filterdir}/Now"
   tmp = Time.now.asctime
   tmpfolder = tmp.split[1] + tmp.split[4]
   @folderdir = "#{filterdir}/#{tmpfolder}"
   @logfile = File.open("#{filterdir}/log.#{tmpfolder}","a")

   @filters = Hash.new
   @filterKeys = Array.new
 end

 def default_folder
   @default_folder
 end

 def folderdir
   @folderdir
 end

 def addFilter(filter,type='envelope') 
   my_type = 'envelope' unless type 
   if ( filter.test.respond_to?(:match) ) && ( defined? filter.folder ) then
     if @filters.has_key? my_type then
       @filters[my_type] << filter
     else 
       @filters[my_type] = Array.new
       @filters[my_type] << filter
       @filterKeys << my_type
     end 
   else
     raise ArgumentError 
   end 
 end

 def process(msg)
   if msg.header then 
     @filterKeys.each do |key| 
       if msg.header.mbox_from && ( key == 'envelope')  then
	 test_string = msg.header.mbox_from
       else 
	 test_string = msg.header[key]
       end
       if test_string then 
	 activeFilter = @filters[key].detect { |filter| filter.test.match(test_string) }
	 if activeFilter then
	   if activeFilter.has_proc? then
	     folder = activeFilter.call(msg)
	     if folder then
	       return "#{@folderdir}/#{folder}"
	     end
	   else
	     # Wrong place to do logging.
	     # self.log("Filing #{msg.header['message-id']} in #{activeFilter.folder}")
	     return "#{@folderdir}/#{activeFilter.folder}"
	   end
	 end
       end
     end #filterKeys
   end #msg.header
   return @default_folder
 end 

 def log (string)
   print "#{string}\n" if ( string =~ /ERROR/ ) 
   @logfile.print "#{string}\n"
 end 

 def initFilters
   filterfile = "#{@filterdir}/filters"
   begin
     File.open(filterfile) do |file|
       while ( line = file.gets ) do 
	 unless ( line =~ /^#/ ) then
	   flag_string , mbox , type = line.split
	   if ( flag_string && mbox ) then
	     self.addFilter(Filter.new(Regexp.new(Regexp.quote(flag_string)),mbox),type) 
	   end
	 end
       end
     end
   rescue
     print "Error in reading #{filterfile}"
     exit
   end
 end 

end #FilterProcess

# For dealing with file
class IfileProcess

  def initialize(filterdir,default_folder)
    @filterdir = filterdir
    @default_folder = default_folder
    @ifile = Ifile::Process.new
    @same = Hash.new
    @same["unix-admin"] = "oldmail"
    @same["admin-log"]  = "oldmail"
    @same[default_folder] = "oldmail"
  end

# Return a full fledge path 
  def query(msg)
    results = @ifile.query(msg) 
    best = results[0]["folder"]
    # Where did oldmail score
    # oldmail = results.detect { |result| result["folder"] == "oldmail" }
    if ( best == "oldmail" ) then
      return @default_folder
    end 
    dest = "#{@filterdir}/#{best}"
    dest.sub!(/\s/,"")
    return dest
  end 

  def add(msg,destination)
    if ( @same[destination] ) then
      folder = @same[destination]
    else
      folder = destination.sub(@filterdir,"")
      folder.sub!(/[\/]/,"")
      # Deal with incoming/oldmail problem 
      if ( @same[folder]  ) 
	folder = @same[folder] 
      end
    end
#    print "Adding message to :#{folder}:\n"
    @ifile.add(msg,folder)
  end

end #IfileProcess

# This is for real.
begin 
  filterP = FilterProcess.new("foobar","/home/foobar/filtermail")

  begin 
    # Be more paranoid add a timestamp and pid
    backup = "/tmp/foobar.backupspool.#{Time.now.to_i}.#{Process.pid}"
    system("/bin/cp /var/spool/mail/foobar #{backup}")
  rescue
    print "Error making backup... /tmp/foobar.backsupspool"
    exit 
  end 
  emergency = "/tmp/foobar.emergency"

  spool = MailSpool.new("foobar")
  filterP.initFilters

#Ifile interface. 
  ifile = IfileProcess.new(filterP.folderdir,filterP.default_folder)

rescue => error
  print "Error in startup. #{error.inspect}\n"
  exit
end

report = Hash.new(0)

if ( spool.update) then 
  messages = spool.messages

  messages.each do |msg|
    # Where does it go ? 
    destination = filterP.process(msg)
    # Do some destination dependant checks. 
    if ( destination == filterP.default_folder ) then
      begin
	new_destination = ifile.query(msg)
        filterP.log("INFO: Ifile redirect #{destination} => #{new_destination}")
	destination = new_destination
      rescue
	filterP.log("ERROR: using ifile.query") 
      end
    end
    report[destination] = report[destination] + 1  
    # Write to destination
    filterP.log("INFO: Filing #{msg.header['message-id']} in #{destination}")
    # Check for error
    begin
      RFilter::Deliver.deliver_mbox(destination,msg)
    rescue
      RFilter::Deliver.deliver_mbox(emergency,msg)
      filterP.log("ERROR: Delivering to #{emergency}")
    end
    # Add to ifile database. 
    begin
      ifile.add(msg,destination)
    rescue => error
      filterP.log("ERROR: using ifile.add #{error.inspect}") 
    end 
  end
end

# print report
report.each do |folder, count|
  print "#{folder} : #{count} new messages\n"
end


-----BEGIN PGP SIGNATURE-----
Version: 2.6.2

iQCVAwUBPaLtDWTWTAjn5N/lAQHBvAP/Zg5+4Zh8qPNQuHg98naoktqzhovZh4ws
61wlZzsQ13SKStl40hF3TqS7x2BbvgYNJnl9r5iC6qXpraxByQVU59yntEY3HP14
22LPvL3r3rIxgzbbO0sQ7mCF2s/Gw4wVfk0xHxgM4zSjZ9uKqNz3KdS8B0eKtUR3
TTSGrodsN6g=
=AaZQ
-----END PGP SIGNATURE-----