#!/usr/bin/ruby

require 'getoptlong'
require 'find'

# Copyright by Timo Proescholdt < proescho@informatik.uni-muenchen.de >
# this script lives at http://www.cip.informatik.uni-muenchen.de/~proescho/progs/mailq.rb
# feel free to modify, copy and reuse it and to give feedback if you find it (un)usefull
# BE CAREFULL on large queues, this script has to read every queue file which can cause enourmous load. Memory usage should be low as a file is only stored in memory at one time.

# call with no arguments to see usage.

MAXARGS=1000
QUEUE="/var/spool/postfix"
#QUEUE="/tmp/spam_queue"
POSTCAT="/usr/sbin/postcat"
MAX_FILESIZE=1024*1024 # 1MB

class MyRegexp < Regexp

	def initialize(regexp,target,bool)
		raise ArgumentError unless regexp.is_a? String
		raise ArgumentError unless (target.is_a? String or target==nil)

		@target=target
		@bool=bool
		super(regexp)
	end
	attr_reader :target

	def apply_to(qf)
		raise ArgumentError unless qf.is_a? QueueFile

		case @target
		when nil
		    ret = qf.search_all(self)
		when ":h"
		    ret = qf.search_header(self)
		when ":b"
		    ret = qf.search_body(self)
		when ":e"
		    ret = qf.search_envelope(self)
		end
		if @bool then
		   return ret
		else
		   return !ret
		end
	end
	
end



class QueueFile

	attr_reader :id

	def initialize(raw)

		@envelope=Array.new
		@body=Array.new
		@header=Array.new
		@header_ex=Array.new


		lines = raw.split("\n")

		mode = "env"

		lines.each {|l|
  		#*** MESSAGE CONTENTS 0/0DBAB4715E ***

		  if l=~/^\*\*\* MESSAGE CONTENTS.*?([A-Z0-9]+) \*\*\*/ then
			mode="header" 
			@id=$1  
		  end
		  mode="end"  if l=~/^\*\*\* HEADER EXTRACTED/
		  mode="body" if mode=="header" and l=~/^\s*$/ 
		  
		  case mode

		  when "env"
			@envelope<<l
		  when "header"
			@header<<l
		  when "body"
			@body<<l
		  when "end"
			@header_ex<<l
		  end
		}
		raise Exception.new("fehler beim id Parsen #{raw}") if @id==nil

	end

	def sender
		sender=@envelope.grep(/^sender:/)
		sender =~ /^sender:(.*?)$/ 
		if $1 != "" then
		   return  @header.grep(/^From:/)
		else
		   return sender 
		end
	end

	def recipient
		@envelope.grep(/^recipient:/)
	end

	def arrival_time
		@envelope.grep(/arrival_time:/)
	end

	def to_s 

		ret=Array.new

		ret << "QUEUE id #{@id}"

		ret << "ENVELOPE"
		ret.concat(@envelope) 

		ret << "HEADER"
		ret.concat(@header) 

		ret << "BODY"
		ret.concat(@body) 

		ret << "HEADER_EX"
		ret.concat(@header_ex) 
	
		return ret.join("\n")

	end

	def search_all(regexp)
		search_generic(regexp,@header) or
		search_generic(regexp,@body) or
		search_generic(regexp,@envelope)
	end

	def search_header(regexp)
		search_generic(regexp,@header)
	end

	def search_body(regexp)
		search_generic(regexp,@body)
	end

	def search_envelope(regexp)
		search_generic(regexp,@envelope)
	end

	def search_generic(regexp,target)
		raise ArgumentError unless regexp.is_a? Regexp
		match = target.grep(regexp)
		if !match.empty? then
			puts @id+" matcht #{match}" if @@verbose
			return true
		else
			return false
		end
	end
	private :search_generic

end


def print_help
	 pp = Array.new
	 pp <<  "usage: "+$0+" [--deferred -d ] [--active -a] [--hold -h] [--verbose -v] [--help] [--info] [--size size ] [ --output ] regexps.."
	 pp << "\n"
	 pp <<  "prints the queue ids of the queue files matching ALL the specified regexps"
	 pp <<  "if no queue is specified all queues are searched, mutiple queues are possible"
	 pp <<  "a regexp has the following format: !?/something/(:[ehb])? h header, e envelope, b body"
	 pp <<  "matches against whole mail if :[ehb] is not specified, ! negates the expression"
	 pp <<  "regexps should be quoted to be protected by the shell"
	 pp <<  "--info adds sender and recipient information"
	 pp <<  "--size size skips all files which are > size"
	 pp <<  "--output does not print any information except the queue files, if specified (to be used in sripts)"
	 pp <<  "\n\texample: #{$0} --active '/From: timo/:h' '/so ein grind/:b' '!/test/'\n"
	 pp <<  "matches all queue files in the active queue which are from timo"
	 pp <<  "containing \"so ein grind\" in the body and NOT containing test ANYWHERE in the file"
	 pp << "\n\n"

	puts pp.join("\n\t")
	exit(0)
end

opts = GetoptLong.new(
  [ "--deferred", "-d",            GetoptLong::NO_ARGUMENT ],
  [ "--active",   "-a",            GetoptLong::NO_ARGUMENT ],
  [ "--hold",   "-h", 		   GetoptLong::NO_ARGUMENT ],
  [ "--verbose",   "-v", 	   GetoptLong::NO_ARGUMENT ],
  [ "--help",   		   GetoptLong::NO_ARGUMENT ],
  [ "--info", "-i",   		   GetoptLong::NO_ARGUMENT ],
  [ "--size", "-s",   		   GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--output", "-o",		   GetoptLong::NO_ARGUMENT ]
)

@@verbose=false
@@info=false
@@output=true


queues=Array.new
max_filesize=nil

opts.each do |opt, arg|
  @@verbose=true if opt=="--verbose" 
  @@info=true if opt=="--info" 
  @@output=false if opt=="--output"
  print_help if opt=="--help" 
  if opt=="--size" then
	if arg then
	  max_filesize=arg 
	else
	  max_filesize=MAX_FILESIZE
	end
  end
  queues<<"#{QUEUE}/active" if opt=="--active"
  queues<<"#{QUEUE}/hold" if opt=="--hold"
  queues<<"#{QUEUE}/deferred" if opt=="--deferred"
end

print_help if ARGV.empty?

if queues.empty? then
  queues<<"#{QUEUE}/active" << "#{QUEUE}/hold" << "#{QUEUE}/deferred"
end

# parse regular expressions given on cmdline

regexps=Array.new

ARGV.each { |a|
	raise ArgumentError unless a=~/^\s*(!)?\/([^\/]+)\/(:[ehb])?\s*$/
	#puts "die regexp ist #{$2}, das Target #{$3}"
	#puts "das ganze negiert" if $1=="!"
	regexps<<MyRegexp.new($2,$3,$1!="!")
}



files = Array.new
filesmd=Array.new

queues.each { |q|

   puts "processing queue "+q if @@output
   Find.find(q) { |path|
	if FileTest.file?(path) and path !~ /\.rb/ then
	if max_filesize and File.size(path)>max_filesize.to_i then
		puts "skipping #{path} due to filesize" if @@output
		next
	end
		files<<path
	end
   }
}

j=-1
files.each_index { |i|

	if i%MAXARGS==0 then 
		j+=1
		filesmd[j]=Array.new
	end
	filesmd[j]<<files[i]
}

filesmd.each { |md|
	res = `#{POSTCAT} #{md.join(" ")}`
	res.split(/\*\*\*\s+MESSAGE FILE END.*?\*\*\*$/).each { |raw|
		next if raw=="\n"
		qf=QueueFile.new(raw)
		bool=false
		regexps.each {|re|
			if re.apply_to(qf) then
			 bool=true
			else 
			 bool=false
			 break
			end
		}	
		if bool then
		   puts qf.id 
		   if @@info then
		    puts "\t"+qf.sender.join(",")
		    puts "\t"+qf.recipient.join(",")
		    puts "\t"+qf.arrival_time.join(",")
		   end
		end
	}
}

