gurgitate-mail/CHANGELOG 0000644 0000765 0000144 00000021262 10747761052 014746 0 ustar dagbrown users 1.10.0:
- Added support for MH mailboxes
1.9.1:
- Made a bunch of little changes to ensure that gurgitate-mail is compatible
with Ruby 1.9.0 (whilst not sacrificing compatibility with Ruby 1.8.x)
- Added the unit tests to the tarball for those who are curious
1.9.0:
- Added the ability to create new Gurgitate::Mailmessage objects with the
"create" constructor--instead of giving it the text of a message to
parse, you give it headers as a hash and it puts the mail message together.
1.8.3:
- Added an "exit 75 (TEMPFAIL)" for when it tries its best to deliver a mail
message anywhere at all and fails no matter how hard it tries. With a
sensible MTA, this should result in the message being requeued and
reprocessed later.
1.8.2:
- Fixed a bug discovered by TheWordNerd where gurgitate was writing to
the default spool after every config file was processed, instead of
holding off until all of them had gone through.
1.8.1:
- Whoops! Fixed a bug (thanks tchan!) in argument parsing that was causing
to parsing to break.
1.8.0:
- Let you (only TEN FREAKIN' YEARS LATE) explicitly specify sender and
receivers on the command line.
- Added methods (sub and sub!) to HeaderBag to allow you to alter the contents
of headers
1.7.2:
- Added gem support
1.7.1:
- Fixed Yet Another Header Parsing Bug[tm]
1.7.0:
- Changed it so that when you say "folderstyle Maildir", it changes the
default mail spool dir and mail spool file to $HOME and $HOME/Maildir
respectively.
- Changed the way that config params work. Now instead of having to say:
self.sendmail = "/usr/sbin/sendmail"
you can say:
sendmail "/usr/sbin/sendmail"
Sort of in the style of attr_reader and attr_writer, or in the style
of some other programs' config files.
- Added -f option to select rules files from the command-line
1.6.3:
- Restored Gurgitate::Gurgitate#process() with a block, which seemed to have
disappeared with the config file commotion.
- Added mention of the systemwide configuration files to the manual. Which,
by the way, could still use something of an overhaul. Way too much stuff
is still SEKRIT.
1.6.2:
- Fixed a bug introduced in the last code reorg, whereby it would forget to
default to saving mail to the spool
1.6.1:
- Fixed a bug introduced in the last code reorg, whereby it would forget to
default to saving mail to the spool
- Fixed file permissions in the tarball
- Fixed a really stupid bug with Maildir folder creation where it made invalid
mail directories.
1.6.0:
- Added site-wide gurgitate-rules-file capability.
- Made it so that headers that start with a number pass through without
breaking stuff, which is a shame, because those are quite illegal as
far as I know.
- Also, headers with a . in the *header* name should pass though.
- ALSO, headers with a leading - in the header name, likewise.
- Fixed a (very minor) maildir-writing bug involving a misnamed variable, which
might trigger were gurgitate to be used as an LMTP process or a Sendmail
milter.
1.5.3:
- Fixed a problem caused by me not touching filter in way too long:
there were namespace and parameter problems.
1.5.2:
- Made it not blow up when it encounters a header of the form
"To:\n emailaddress@example.com" (as seen in email messages from
the obscure Japanese email client Becky)
1.5.1:
- Made it use Postfix's heuristic for determining whether a nonexistent
mailbox is a mail spool or a Maildir by looking for a slash on the end
of the mailbox's name.
- Added code from Bertram Scharpf to make error-in-rules handling
a little more graceful
1.5:
- Made it not throw an exception on (illegal, mind) headers with
underscores in their names.
- Added a thing to the "filter" method so that you can say something like
filter("spamc") do
if headers["X-Spam"] =~ "Yes" then
delete
end
end
if you want to.
- Also added the ability to put
folderstyle = Maildir
into your .gurgitate-rules.rb to tell it that if it doesn't find
a mailbox, it should create a Maildir mailbox rather than its
usual default (mbox).
1.4.1:
I said I'd given up on minor changes? I lied.
- Had it assume that if a file isn't present, that it's a MBox mailbox,
and create it.
- Changed the constructor for Gurgitate to take all login information
from the effective UID instead of trusting the real UID and the
EUID to be the same.
1.4:
I seem to have given up on minor changes. More big stuff.
- Changed the API! (WARNING WARNING WARNING) Now Message#to_s
returns the email message without the "From " line. You get that
with Message#to_mbox
- Broke the code into lots of little pieces instead of the one great
big file. I hope this makes it slightly easier for people to
understand--it certainly made it easier for me
- Added maildir delivery! Plus a sort of mechanism for delivering to
various kinds of mailboxes.
1.3:
Some pretty big things here (in my opinion), so I figured it was
worth making it a 1.3 release instead of 1.2.2.
- Added the ability to say
headers["From", "Subject", "Cc"] =~ /hi there/
in your .gurgitate-rules, and have it do the right thing.
- Added "to" so you can now say
if to =~ /mailing-list/ then ...;end
instead of the cumbersome
if headers.matches(["To","Cc"],/mailing-list/) then ...; end
syntax which I just plain ol' hated.
- Made it possible to change the contents of headers.
- Made the man page not claim to be user-contributed Perl
documentation :-)
- Made it deal correctly with headers that have tabs between the colon
and the data, instead of spaces (bug #154).
1.2.1:
- Made it NOT BLOW UP when you give it an email with a header that
has no contents. (Aren't those supposed to be illegal? Regardless,
I have to deal with whatever turns up, be it kosher or no)
- Made its installer also politely install a man page
1.2:
- Fixed some header handling which was kind of, er, broken.
Specifically, I'd get an exception on a header which looked
like:
Subject:
Hi there
The only kind of email that I've ever seen this kind of header
on has been spam, but nonetheless, it's hardly fair for a mail
filter to blow up just because it's given crappy input.
- Made the comments more rdoc-friendly.
Fixed the handling of multi-line headers--it was getting it
RONG RONG RONG (but I've probably implemented this RONG RONG
RONG too).
- Made it so that as well as saying
g=Gurgitate::Gurgitate.new(filehandle)
if g.head =~ /evil@bad.com/ then delete end
you can also say
Gurgitate::Gurgitate.new(filehandle) do
if head =~ /evil@bad.com/ then delete end
end
Which gives you another place to put your .gurgitate-rules.
- Made install.rb work as both a script as a library, because
I switched to using rake to build things, rather than make.
- Made it canonicalize headers to a standard capitalization,
because some SMTP client (*cough*virus*cough*) is incorrectly
sending out all-uppercase headers.
1.1.3:
- Put the whole thing into a big module, and added some extra
comments to make it more rdoc-friendly. You shouldn't need to
change your .gurgitate-rules.rb if you use it like that, but
if you use it as a module, you'll need to prefix 'Gurgitate::'
to your Gurgitate object construction.
1.1.2: maintenance release
- Changed gurgitate-mail so that it compiles cleanly under Ruby 1.8,
and made it work with no warnings.
1.1.1: maintenance release
- Fixed the regexes for my own email addresses--they had backslashes
in strange place, causing people to ask odd questions.
Fixed a typo (thanks to Tom Wadlow)--I was using an undefined
local variable "sendmail" instead of a class variable "@sendmail".
1.1:
- Implemented Pavel Kolar's suggestion that if you filter email
through an external program, you might want the results of that,
instead of just a return code.
- Added a default value to the Gurgitate initializer parameter.
- Added an "install.rb" script to install the script and library
files into their proper locations.
- Pulled the "Gurgitate" class and friends out into a separate file,
and made "gurgitate-mail" into a tiny script which does a
"require" to pull the gurgitate-mail stuff in. This should make
it easier to extend in the future.
- Added this file to the distribution. :-)
1.0.1: maintenance release
- Small code cleanup--changed some accessors to use attr_*.
1.0:
- Initial release of gurgitate-mail
gurgitate-mail/INSTALL 0000644 0000765 0000144 00000000123 07720326045 014552 0 ustar dagbrown users To install gurgitate-mail, simply run the "install.rb" script:
ruby install.rb
gurgitate-mail/install.rb 0000644 0000765 0000144 00000005222 10747013737 015525 0 ustar dagbrown users #!/usr/bin/ruby -w
require "rbconfig"
require "fileutils"
module Gurgitate
Package = "gurgitate-mail"
class Install
def self.mkdir(d)
print "Creating #{d}..."
begin
Dir.mkdir(d)
print "\n"
rescue Errno::EEXIST
if FileTest.directory? d
puts "no need, it's already there."
else
puts "there's something else there already."
raise
end
rescue Errno::ENOENT
puts "its parent doesn't exist!"
raise
end
end
def self.install(prefix=nil)
include Config
if prefix then
bindir = File.join prefix, "bin"; mkdir bindir
dest = File.join prefix, "lib"; mkdir dest
mkdir File.join(prefix, "man")
mandir = File.join prefix, "man", "man1"; mkdir mandir
else
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
sitedir = CONFIG["sitedir"]
bindir = CONFIG["bindir"]
mandir = File.join(CONFIG["mandir"],"man1")
dest = CONFIG["sitelibdir"]
end
destgur = File.join(dest,"gurgitate")
destdel = File.join(destgur,"deliver")
print "Installing #{Package}.rb in #{dest}...\n"
FileUtils.install("#{Package}.rb", dest, :mode => 0644)
mkdir destgur
Dir.glob(File.join("gurgitate","*.rb")).each { |f|
puts "Installing #{f} in #{destgur}..."
FileUtils.install(f,destgur)
}
mkdir destdel
Dir.glob(File.join(File.join("gurgitate","deliver"),"*.rb")).each {
|f|
puts "Installing #{f} in #{destdel}..."
FileUtils.install(f,destdel)
}
print "Installing #{Package}.1 in #{mandir}...\n"
FileUtils.install("#{Package}.man","#{mandir}/#{Package}.1",
:mode => 0644)
print "Installing #{Package} in #{bindir}...\n"
# Not so simple; need to put in the shebang line
from_f=File.open("gurgitate-mail")
to_f=File.open("#{bindir}/gurgitate-mail","w")
to_f.print("#!#{bindir}/ruby -w\n\n")
from_f.each do |l|
to_f.print l
end
to_f.close()
from_f.close()
File.chmod(0755,"#{bindir}/gurgitate-mail")
end
end
end
if __FILE__ == $0 then
Gurgitate::Install.install()
end
gurgitate-mail/gurgitate-mail.rb 0000644 0000765 0000144 00000025004 10747761261 016774 0 ustar dagbrown users #!/opt/bin/ruby
#------------------------------------------------------------------------
# Mail filter package
#------------------------------------------------------------------------
require 'etc'
require 'gurgitate/mailmessage'
require 'gurgitate/deliver'
module Gurgitate
#========================================================================
# The actual gurgitator; reads a message and then it can do
# other stuff with it, like save to a mailbox or forward
# it somewhere else.
class Gurgitate < Mailmessage
include Deliver
# Instead of the usual attributes, I went with a
# reader-is-writer type thing (as seen quite often in Perl and
# C++ code) so that in your .gurgitate-rules, you can say
#
# maildir "#{homedir}/Mail"
# sendmail "/usr/sbin/sendmail"
# spoolfile "Maildir"
# spooldir homedir
#
# This is because of an oddity in Ruby where, even if an
# accessor exists in the current object, if you say:
# name = value
# it'll always create a local variable. Not quite what you
# want when you're trying to set a config parameter. You have
# to say "self.name = value", which (I think) is ugly.
#
# In the interests of promoting harmony, of course, the previous
# syntax will continue to work.
def self.attr_configparam(*syms)
syms.each do |sym|
class_eval %/
def #{sym} (*vals)
if vals.length == 1
@#{sym} = vals[0]
elsif vals.length == 0
@#{sym}
else
raise ArgumentError,
"wrong number of arguments " +
"(\#{vals.length} for 0 or 1)"
end
end
# Don't break it for the nice people who use
# old-style accessors though. Breaking people's
# .gurgitate-rules is a bad idea.
attr_writer :#{sym}
/
end
end
# The directory you want to put mail folders into
attr_configparam :maildir
# The path to your log file
attr_configparam :logfile
# The full path of your "sendmail" program
attr_configparam :sendmail
# Your home directory
attr_configparam :homedir
# Your default mail spool
attr_configparam :spoolfile
# The directory where user mail spools live
attr_configparam :spooldir
# What kind of mailboxes you prefer
# attr_configparam :folderstyle
# What kind of mailboxes you prefer
def folderstyle(*style)
if style.length == 0 then
@folderstyle
elsif style.length == 1 then
if style[0] == Maildir then
spooldir homedir
spoolfile File.join(spooldir,"Maildir")
maildir spoolfile
elsif style[0] == MH then
mh_profile_path = File.join(ENV["HOME"],".mh_profile")
if File.exists?(mh_profile_path) then
mh_profile = YAML.load(File.read(mh_profile_path))
maildir mh_profile["Path"]
else
maildir File.join(ENV["HOME"],"Mail")
end
spoolfile File.join(maildir,"inbox")
else
spooldir "/var/spool/mail"
spoolfile File.join(spooldir, @passwd.name)
end
@folderstyle = style[0]
else
raise ArgumentError, "wrong number of arguments "+
"(#{style.length} for 0 or 1)"
end
@folderstyle
end
# Set config params to defaults, read in mail message from
# +input+
# input::
# Either the text of the email message in RFC-822 format,
# or a filehandle where the email message can be read from
# recipient::
# The contents of the envelope recipient parameter
# sender::
# The envelope sender parameter
# spooldir::
# The location of the mail spools directory.
def initialize(input=nil,
recipient=nil,
sender=nil,
spooldir="/var/spool/mail",
&block)
@passwd = Etc.getpwuid
@homedir = @passwd.dir;
@maildir = File.join(@passwd.dir,"Mail")
@logfile = File.join(@passwd.dir,".gurgitate.log")
@sendmail = "/usr/lib/sendmail"
@spooldir = spooldir
@spoolfile = File.join(@spooldir,@passwd.name )
@folderstyle = MBox
@rules = []
input_text = ""
input.each_line do |l| input_text << l end
super(input_text, recipient, sender)
instance_eval(&block) if block_given?
end
def add_rules(filename, options = {})
if not Hash === options
raise ArgumentError.new("Expected hash of options")
end
if filename == :default
filename=homedir+"/.gurgitate-rules"
end
if not FileTest.exist?(filename)
filename = filename + '.rb'
end
if not FileTest.exist?(filename)
if options.has_key?(:user)
log("#{filename} does not exist.")
end
return false
end
if FileTest.file?(filename) and
( ( not options.has_key? :system and
FileTest.owned?(filename) ) or
( options.has_key? :system and
options[:system] == true and
File.stat(filename).uid == 0 ) ) and
FileTest.readable?(filename)
@rules << filename
else
log("#{filename} has bad permissions or ownership, not using rules")
return false
end
end
# Deletes the current message.
def delete
# Well, nothing here, really.
end
# This is a neat one. You can get any header as a method.
# Say, if you want the header "X-Face", then you call
# x_face and that gets it for you. It raises NameError if
# that header isn't found.
# meth::
# The method that the caller tried to call which isn't
# handled any other way.
def method_missing(meth)
headername=meth.to_s.split(/_/).map {|x| x.capitalize}.join("-")
if headers[headername] then
return headers[headername]
else
raise NameError,"undefined local variable or method, or header not found `#{meth}' for #{self}:#{self.class}"
end
end
# Forwards the message to +address+.
# address::
# A valid email address to forward the message to.
def forward(address)
self.log "Forwarding to "+address
IO.popen(@sendmail+" "+address,"w") do |f|
f.print(self.to_s)
end
end
# Writes +message+ to the log file.
def log(message)
if(@logfile)then
File.open(@logfile,"a") do |f|
f.flock(File::LOCK_EX)
f.print(Time.new.to_s+" "+message+"\n")
f.flock(File::LOCK_UN)
end
end
end
# Pipes the message through +program+. If +program+
# fails, puts the message into +spoolfile+
def pipe(program)
self.log "Piping through "+program
IO.popen(program,"w") do |f|
f.print(self.to_s)
end
return $?>>8
end
# Pipes the message through +program+, and returns another
# +Gurgitate+ object containing the output of the filter
def filter(program,&block)
self.log "Filtering with "+program
IO.popen("-","w+") do |filter|
unless filter then
begin
exec(program)
rescue
exit! # should not get here anyway
end
else
if fork
filter.close_write
g=Gurgitate.new(filter)
g.instance_eval(&block) if block_given?
return g
else
begin
filter.close_read
filter.print to_s
filter.close
rescue
nil
ensure
exit!
end
end
end
end
end
# Processes your .gurgitate-rules.rb.
def process(&block)
begin
if @rules.size > 0 or block
@rules.each do |configfilespec|
begin
eval File.new(configfilespec).read, nil,
configfilespec
rescue ScriptError
log "Couldn't load #{configfilespec}: "+$!
save(spoolfile)
rescue Exception
log "Error while executing #{configfilespec}: #{$!}"
$@.each { |tr| log "Backtrace: #{tr}" }
folderstyle = MBox
save(spoolfile)
end
end
if block
instance_eval(&block)
end
log "Mail not covered by rules, saving to default spool"
save(spoolfile)
else
save(spoolfile)
end
rescue Exception
log "Error while executing rules: #{$!}"
$@.each { |tr| log "Backtrace: #{tr}" }
log "Attempting to save to spoolfile after error"
folderstyle = MBox
save(spoolfile)
end
end
end
end
gurgitate-mail/gurgitate-mail 0000644 0000765 0000144 00000002341 10747761261 016371 0 ustar dagbrown users #------------------------------------------------------------------------
# Mail filter invocation script
#------------------------------------------------------------------------
require "gurgitate-mail"
require 'optparse'
# change this on installation to taste
GLOBAL_RULES="/etc/gurgitate-rules"
GLOBAL_RULES_POST="/etc/gurgitate-rules-default"
commandline_files = []
mail_sender = nil
opts = OptionParser.new do |o|
o.on "-f FILE", "--file FILE", "Use FILE as a rules file" do |file|
commandline_files << file
end
o.on "-s SENDER", "--sender SENDER", "Use SENDER as sender" do |sender|
mail_sender = sender
end
o.on_tail "-h", "--help", "Show this message" do
puts opts
exit
end
end
mail_recipients = opts.parse(ARGV)
if mail_recipients.length == 0 then
mail_recipients = nil
end
gurgitate = Gurgitate::Gurgitate.new(STDIN, mail_recipients, mail_sender)
if commandline_files.length > 0
commandline_files.each do |file|
gurgitate.add_rules(file, :user => true)
end
else
gurgitate.add_rules(GLOBAL_RULES, :system => true)
gurgitate.add_rules(:default)
gurgitate.add_rules(GLOBAL_RULES_POST, :system => true)
end
begin
gurgitate.process
rescue CouldNotProcessMailError
exit 75
end
gurgitate-mail/gurgitate-mail.html 0000644 0000765 0000144 00000043243 10747761262 017343 0 ustar dagbrown users
gurgitate-mail is a program which reads your mail and filters
it according to the .gurgitate-rules.rb file in your home
directory. The configuration file uses Ruby syntax and is thus
quite flexible.
It's generally invoked either through your .forward file:
"|/path/to/gurgitate-mail"
Or through your .procmailrc file:
:0:
| /path/to/gurgitate-mail
Alternatively, if you're the sysadmin at your site, or your sysadmin
is friendly, you can use gurgitate-mail as a local delivery agent. For
postfix, put
mailbox_command=/opt/bin/gurgitate-mail
in /etc/postfix/main.cf. If you use any other MTA, and configure
gurgitate-mail as a local delivery agent, please tell me how! I want to
include this in the documentation.
There are three configuration files used by gurgitate-mail: two are
system-wide, and the third, is the user rules file.
The two system-wide configuration files are /etc/gurgitate-rules and
/etc/gurgitate-rules-default. These are processed before and
after the user rules, respectively.
/etc/gurgitate-rules is used to handle system-wide filtering
needs: setting the default mailbox style to Maildir rather than
the default MBox, setting the spool directory, things like that.
The user configuration file is $HOME/.gurgitate-rules (or,
alternatively, $HOME/.gurgitate-rules.rb. Either work). You
put your own rules here. If the user configuration file doesn't
encounter a "return" during processing, then the additional rules
contained in /etc/gurgitate-rules-default are run. If that
also doesn't return, then mail messages are saved into the default
mail spool location.
If the -f option is used on the commandline, then the file
specified will be used and the default rules will not. The -f
option can be used more than once:
The full path to the sendmail program, used to deliver mail. This
can be any program that takes as its parameters the list of addresses
to deliver mail to, and that takes a mail message on standard input.
This contains the body of the email message. As of yet, there's
nothing really interesting which you can do with this, apart from
assigning to it; you can rewrite the body of an email message this
way. Dealing with attachments is planned for a future release of
gurgitate-mail.
maildir
The directory which contains the folders, used by the save
method when you specify a folder as "=folder" (like Elm).
Defaults to "$HOME/Mail".
homedir
Your home directory. Read-only.
logfile
The location of the gurgitate-mail logfile. If set to nil,
then no logging is done. Defaults to "$HOME/.gurgitate.log".
sendmail
The location of the sendmail program. Used by the forward
method. Defaults to "/usr/lib/sendmail".
Returns true if the header name matches the regular
expression regex. If name is an array of header names, then
it returns true if at least one of the headers matches. Useful
for testing whether both "To:" and "Cc:" headers match.
from
Returns the envelope "from" address of the email message. Note
that this is the same as the bare "from".
This saves the message to a mailbox. You can specify the mailbox
as a word with an = sign in front of it, in which case it puts it
into maildir. If you don't use the =name format, then you
need to specify an absolute pathname. If it can't write the
message to the file you request it to, it'll attempt to write it
to spoolfile.
This pipes the message through program and returns a new
Gurgitate object containing the filtered mail. (This is handy
for external filters which modify email like, for example,
SpamAssassin, which adds a spam-score header.)
You can also say
filter(program) do
# code here
end
and it yields the newly-created Gurgitate object to the block.
headers
This returns the headers as an object of their own. This object
has its own methods:
This returns a HeaderBag (a subclass of array) containing the
headers you asked for. You can then use the =~ operator on this
result to match the RHS regex with everything in the HeaderBag.
You can change a header's value with headers[name]=newvalue.
This tells gurgitate-mail to stop processing the email message.
If you don't use return, then gurgitate-mail will continue
processing the same mail again with the next rule. If there isn't
a return at the end of gurgitate-rules.rb, then
gurgitate-mail will save the email message in the normal mail
spool.
Here are some examples of gurgitate-mail rules, with
explanations:
if from =~ /ebay.com/ then save("=ebay"); return; end
Any email from eBay (automatic end-of-auction notifications, for
example, and outbid notices) gets filed into the "ebay" folder.
if from =~ /root@/ then save("=root"); return; end
Any email from root (at any host) gets filed into a special
folder. Useful for sysadmins monitoring crontab email.
if headers.matches(["To","Cc"],"webmaster@") then
save("=webmaster")
return
end
Any email with a To: or Cc: line of "sysadmin" is saved to a
"sysadmin" folder. Useful for people with multiple role accounts
redirected to their address.
if headers["Subject"] =~ /\[SPAM\]/ then
save("=spam")
return
end
This is a different syntax for matching patterns against headers.
You can also match multiple headers in the square brackets.
if headers["Subject","Keywords"] =~ /a bad word/ then
save("=swearing")
return
end
Searches for "a bad word" in the Subject and Keywords headers, and
if it's there, saves the email in the "swearing" folder.
if headers.matches(["To","Cc"],"mailing-list@example.com") then
pipe("|rcvstore +mailing-list")
return
end
Any email to a mailing list is piped through "rcvstore" to store
it into an MH folder.
That
headers.matches(["To","Cc"],/regex/)
idiom happens often enough that there's a shorthand for it:
if to =~ /mailing-list@example.com/ then
pipe("|rcvstore +mailing-list")
return
end
Pipes the mail to the mailing list through "rcvstore".
Here are some slightly more clever examples to give you an idea
of what you can do with gurgitate-mail. Let's suppose you have
an email whitelist in a file called $HOME/.friends, so you can
determine whether some email is likely to be spam or not.
Then if someone on your whitelist sends you email, then you
automatically save that into the "inbox" folder:
friends=homedir+"/.friends"
if FileTest.exists?(friends) and FileTest.readable?(friends) then
File.new(friends).each do |friend|
if from =~ friend.chomp then
log "Mail from friend "+friend.chomp
save("=inbox")
return
end
end
end
Okay, if someone sends you email, and it's addressed specifically
to you (and gurgitate-mail hasn't caught it in another form already),
then it might or might not be spam: put it into a "grey" folder:
my_addresses= [ /me@example\.com/i,
/me@example\.org/i,
/me@example\.net/i]; # I have three email addresses
my_addresses.each do |addr|
if headers.matches(["To","Cc"],addr) then
save("=possibly-not-spam")
return
end
end
And after that, if it's not from someone you know, and it's not
addressed to your email address either, then it's probably save to
assume that it's spam:
save("=spam")
return
This can be improved by using a Bayesian filter, though; for
example, Eric Raymond's bogofilter program
(http://bogofilter.sourceforge.net) can be automatically trained
and used with the help of the white/grey/black distinctions.
Taking the example above, I'll adjust it by adding in calls to
bogofilter:
friends=homedir+"/.friends"
if FileTest.exists?(friends) and FileTest.readable?(friends) then
File.new(friends).each do |friend|
if from =~ friend.chomp then
log "Mail from friend "+friend.chomp
pipe("bogofilter -h") # <-- LINE ADDED HERE
save("=inbox")
return
end
end
end
bogofilter -h trains bogofilter that mail from whitelisted-people
is not to be considered spam. Okay, at the end of the
.gurgitate-rules, change
save("=spam")
return
to
save("=spam")
pipe("bogofilter -s")
return
This trains bogofilter that anything which doesn't pass the
rest of the filter should be considered spam. Now for the
interesting bit: Change the bit between these to use "bogofilter"
to decide whether email is to be considered spam or not:
my_addresses= [ /me@example\.com/i,
/me@example\.org/i,
/me@example\.net/i]; # I have three email addresses
my_addresses.each do |addr|
if headers.matches(["To","Cc"],addr) then
if pipe("bogofilter")==1
then
log("bogofilter suspects it might not be spam")
save("=possibly-not-spam")
else
log("bogofilter thinks it's probably spam")
save("=spam")
end
return
end
end
bogofilter has an exit code of "1" if it thinks the message is
not spam, and "0" if it thinks the message is spam.
Hopefully this should give you an idea of the kinds of things that
you can use bogofilter for.
gurgitate-mail/gurgitate-mail.man 0000644 0000765 0000144 00000044353 10747761263 017156 0 ustar dagbrown users .\" Automatically generated by Pod::Man 2.16 (Pod::Simple 3.05)
.\"
.\" Standard preamble:
.\" ========================================================================
.de Sh \" Subsection heading
.br
.if t .Sp
.ne 5
.PP
\fB\\$1\fR
.PP
..
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" Set up some character translations and predefined strings. \*(-- will
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
.\" double quote, and \*(R" will give a right double quote. \*(C+ will
.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
.\" nothing in troff, for use with C<>.
.tr \(*W-
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
.ie n \{\
. ds -- \(*W-
. ds PI pi
. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
. ds L" ""
. ds R" ""
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds -- \|\(em\|
. ds PI \(*p
. ds L" ``
. ds R" ''
'br\}
.\"
.\" Escape single quotes in literal strings from groff's Unicode transform.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\"
.\" If the F register is turned on, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.ie \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. nr % 0
. rr F
.\}
.el \{\
. de IX
..
.\}
.\"
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear. Run. Save yourself. No user-serviceable parts.
. \" fudge factors for nroff and troff
.if n \{\
. ds #H 0
. ds #V .8m
. ds #F .3m
. ds #[ \f1
. ds #] \fP
.\}
.if t \{\
. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
. ds #V .6m
. ds #F 0
. ds #[ \&
. ds #] \&
.\}
. \" simple accents for nroff and troff
.if n \{\
. ds ' \&
. ds ` \&
. ds ^ \&
. ds , \&
. ds ~ ~
. ds /
.\}
.if t \{\
. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
.\}
. \" troff and (daisy-wheel) nroff accents
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
.ds ae a\h'-(\w'a'u*4/10)'e
.ds Ae A\h'-(\w'A'u*4/10)'E
. \" corrections for vroff
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
. \" for low resolution devices (crt and lpr)
.if \n(.H>23 .if \n(.V>19 \
\{\
. ds : e
. ds 8 ss
. ds o a
. ds d- d\h'-1'\(ga
. ds D- D\h'-1'\(hy
. ds th \o'bp'
. ds Th \o'LP'
. ds ae ae
. ds Ae AE
.\}
.rm #[ #] #H #V #F C
.\" ========================================================================
.\"
.IX Title "GURGITATE-MAIL 1"
.TH GURGITATE-MAIL 1 "2006-06-07" "perl v5.10.0" "Gurgitate-Mail"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
.nh
.SH "NAME"
gurgitate\-mail \- an easy\-to\-use mail filter
.SH "SYNOPSIS"
.IX Header "SYNOPSIS"
gurgitate-mail
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
\&\f(CW\*(C`gurgitate\-mail\*(C'\fR is a program which reads your mail and filters
it according to the \fI.gurgitate\-rules.rb\fR file in your home
directory. The configuration file uses Ruby syntax and is thus
quite flexible.
.PP
It's generally invoked either through your \fI.forward\fR file:
.PP
.Vb 1
\& "|/path/to/gurgitate\-mail"
.Ve
.PP
Or through your \fI.procmailrc\fR file:
.PP
.Vb 2
\& :0:
\& | /path/to/gurgitate\-mail
.Ve
.PP
Alternatively, if you're the sysadmin at your site, or your sysadmin
is friendly, you can use gurgitate-mail as a local delivery agent. For
postfix, put
.PP
.Vb 1
\& mailbox_command=/opt/bin/gurgitate\-mail
.Ve
.PP
in \fI/etc/postfix/main.cf\fR. If you use any other \s-1MTA\s0, and configure
gurgitate-mail as a local delivery agent, please tell me how! I want to
include this in the documentation.
.SH "CONFIGURATION FILES"
.IX Header "CONFIGURATION FILES"
There are three configuration files used by gurgitate-mail: two are
system-wide, and the third, is the user rules file.
.PP
The two system-wide configuration files are \fI/etc/gurgitate\-rules\fR and
\&\fI/etc/gurgitate\-rules\-default\fR. These are processed before and
after the user rules, respectively.
.PP
\&\fI/etc/gurgitate\-rules\fR is used to handle system-wide filtering
needs: setting the default mailbox style to Maildir rather than
the default MBox, setting the spool directory, things like that.
.PP
The user configuration file is \fI\f(CI$HOME\fI/.gurgitate\-rules\fR (or,
alternatively, \fI\f(CI$HOME\fI/.gurgitate\-rules.rb\fR. Either work). You
put your own rules here. If the user configuration file doesn't
encounter a \*(L"return\*(R" during processing, then the additional rules
contained in \fI/etc/gurgitate\-rules\-default\fR are run. If that
also doesn't return, then mail messages are saved into the default
mail spool location.
.PP
If the \f(CW\*(C`\-f\*(C'\fR option is used on the commandline, then the file
specified will be used and the default rules will not. The \f(CW\*(C`\-f\*(C'\fR
option can be used more than once:
.PP
.Vb 1
\& gurgitate\-mail \-f test\-rules \-f additional\-rules
.Ve
.SH "CONFIGURATION PARAMETERS"
.IX Header "CONFIGURATION PARAMETERS"
There are several parameters that you can set to change the way that
gurgitate-mail behaves. You set a config parameter by saying, for
instance:
.PP
.Vb 1
\& sendmail "/usr/sbin/sendmail"
.Ve
.PP
which sets the \*(L"sendmail\*(R" parameter to \*(L"/usr/sbin/sendmail\*(R".
.IP "maildir" 5
.IX Item "maildir"
The directory you want to put mail folders into. This defaults to
\&\f(CW$HOME\fR/Mail.
.IP "logfile" 5
.IX Item "logfile"
Where you went gurgitate-mail's log messages to go to. The standard
location for this is \f(CW$HOME\fR/.gurgitate.log
.IP "sendmail" 5
.IX Item "sendmail"
The full path to the sendmail program, used to deliver mail. This
can be any program that takes as its parameters the list of addresses
to deliver mail to, and that takes a mail message on standard input.
.IP "homedir" 5
.IX Item "homedir"
The full path of your home directory. This defaults to whatever your
actual home directory is.
.IP "spooldir" 5
.IX Item "spooldir"
The path where the system's mail spools goes to. This defaults to
\&\*(L"/var/spool/mail\*(R". On a Maildir system, this should be set to the
same as \*(L"homedir\*(R".
.IP "spoolfile" 5
.IX Item "spoolfile"
The mail spool file component of the full path of your mail spool.
This is generally your username. Maildir users should set this to
\&\*(L"Maildir\*(R".
.IP "folderstyle" 5
.IX Item "folderstyle"
The style of folders you prefer. This can be (at the moment) either
MBox or Maildir.
.SH "FILTER RULES"
.IX Header "FILTER RULES"
The filter rules are a series of Ruby statements, with the
following methods and variables available:
.Sh "Variables"
.IX Subsection "Variables"
.IP "from" 5
.IX Item "from"
This contains the envelope \*(L"from\*(R" address of the email message.
(Note that this isn't necessarily the same as the contents of the
\&\*(L"From:\*(R" header)
.IP "headers" 5
.IX Item "headers"
This is an object containing the headers of the message. There
are several methods that come with this object:
.IP "body" 5
.IX Item "body"
This contains the body of the email message. As of yet, there's
nothing really interesting which you can do with this, apart from
assigning to it; you can rewrite the body of an email message this
way. Dealing with attachments is planned for a future release of
\&\f(CW\*(C`gurgitate\-mail\*(C'\fR.
.IP "maildir" 5
.IX Item "maildir"
The directory which contains the folders, used by the \f(CW\*(C`save\*(C'\fR
method when you specify a folder as "=\fIfolder\fR\*(L" (like Elm).
Defaults to \*(R"$HOME/Mail".
.IP "homedir" 5
.IX Item "homedir"
Your home directory. Read-only.
.IP "logfile" 5
.IX Item "logfile"
The location of the \f(CW\*(C`gurgitate\-mail\*(C'\fR logfile. If set to \f(CW\*(C`nil\*(C'\fR,
then no logging is done. Defaults to \*(L"$HOME/.gurgitate.log\*(R".
.IP "sendmail" 5
.IX Item "sendmail"
The location of the \f(CW\*(C`sendmail\*(C'\fR program. Used by the \f(CW\*(C`forward\*(C'\fR
method. Defaults to \*(L"/usr/lib/sendmail\*(R".
.IP "spoolfile" 5
.IX Item "spoolfile"
The location of the mail spool. Read-only.
.Sh "Methods"
.IX Subsection "Methods"
.IP "matches(name(s),regex)" 5
.IX Item "matches(name(s),regex)"
Returns \f(CW\*(C`true\*(C'\fR if the header \f(CW\*(C`name\*(C'\fR matches the regular
expression \f(CW\*(C`regex\*(C'\fR. If \f(CW\*(C`name\*(C'\fR is an array of header names, then
it returns true if at least one of the headers matches. Useful
for testing whether both \*(L"To:\*(R" and \*(L"Cc:\*(R" headers match.
.IP "from" 5
.IX Item "from"
Returns the envelope \*(L"from\*(R" address of the email message. Note
that this is the same as the bare \*(L"from\*(R".
.IP "to" 5
.IX Item "to"
Returns a HeaderBag (a kind of array) with the contents of the
\&\*(L"To\*(R" and the \*(L"Cc\*(R" headers.
.IP "to_s" 5
.IX Item "to_s"
As per Ruby convention, returns all the headers as a \f(CW\*(C`String\*(C'\fR object.
.IP "save(mailbox)" 5
.IX Item "save(mailbox)"
This saves the message to a mailbox. You can specify the mailbox
as a word with an = sign in front of it, in which case it puts it
into \f(CW\*(C`maildir\*(C'\fR. If you don't use the =\fIname\fR format, then you
need to specify an absolute pathname. If it can't write the
message to the file you request it to, it'll attempt to write it
to \f(CW\*(C`spoolfile\*(C'\fR.
.IP "forward(address)" 5
.IX Item "forward(address)"
This forwards the email message to another email address.
.IP "pipe(program)" 5
.IX Item "pipe(program)"
This pipes the message through \f(CW\*(C`program\*(C'\fR. \f(CW\*(C`pipe\*(C'\fR returns the
exit code of the program that the message was piped through.
.IP "filter(program)" 5
.IX Item "filter(program)"
This pipes the message through \f(CW\*(C`program\*(C'\fR and returns a new
Gurgitate object containing the filtered mail. (This is handy
for external filters which modify email like, for example,
SpamAssassin, which adds a spam-score header.)
.Sp
You can also say
.Sp
.Vb 3
\& filter(program) do
\& # code here
\& end
.Ve
.Sp
and it yields the newly-created Gurgitate object to the block.
.IP "headers" 5
.IX Item "headers"
This returns the headers as an object of their own. This object
has its own methods:
.RS 5
.IP "headers[*headernames]" 5
.IX Item "headers[*headernames]"
This returns a HeaderBag (a subclass of array) containing the
headers you asked for. You can then use the =~ operator on this
result to match the \s-1RHS\s0 regex with everything in the HeaderBag.
.Sp
You can change a header's value with \f(CW\*(C`headers[name]=newvalue\*(C'\fR.
.IP "headers.match(name,regex)" 5
.IX Item "headers.match(name,regex)"
Matches the header with the name \*(L"name\*(R" against the regex. This
is the same as headers[name] =~ /regex/.
.IP "headers.matches(names,regex)" 5
.IX Item "headers.matches(names,regex)"
Matches the headers with the names \*(L"names\*(R" against the regex.
This is the same as headers[*names] =~ /regex/.
.IP "headers.from" 5
.IX Item "headers.from"
Returns the envelope from. You can change this with
\&\f(CW\*(C`headers.from=newaddress\*(C'\fR too.
.RE
.RS 5
.RE
.IP "return" 5
.IX Item "return"
This tells \f(CW\*(C`gurgitate\-mail\*(C'\fR to stop processing the email message.
If you don't use \f(CW\*(C`return\*(C'\fR, then \f(CW\*(C`gurgitate\-mail\*(C'\fR will continue
processing the same mail again with the next rule. If there isn't
a \f(CW\*(C`return\*(C'\fR at the end of \fIgurgitate\-rules.rb\fR, then
\&\f(CW\*(C`gurgitate\-mail\*(C'\fR will save the email message in the normal mail
spool.
.IP "log(message)" 5
.IX Item "log(message)"
This writes a log message to the log file.
.SH "SIMPLE EXAMPLES"
.IX Header "SIMPLE EXAMPLES"
Here are some examples of \f(CW\*(C`gurgitate\-mail\*(C'\fR rules, with
explanations:
.PP
.Vb 1
\& if from =~ /ebay.com/ then save("=ebay"); return; end
.Ve
.PP
Any email from eBay (automatic end-of-auction notifications, for
example, and outbid notices) gets filed into the \*(L"ebay\*(R" folder.
.PP
.Vb 1
\& if from =~ /root@/ then save("=root"); return; end
.Ve
.PP
Any email from root (at any host) gets filed into a special
folder. Useful for sysadmins monitoring crontab email.
.PP
.Vb 4
\& if headers.matches(["To","Cc"],"webmaster@") then
\& save("=webmaster")
\& return
\& end
.Ve
.PP
Any email with a To: or Cc: line of \*(L"sysadmin\*(R" is saved to a
\&\*(L"sysadmin\*(R" folder. Useful for people with multiple role accounts
redirected to their address.
.PP
.Vb 4
\& if headers["Subject"] =~ /\e[SPAM\e]/ then
\& save("=spam")
\& return
\& end
.Ve
.PP
This is a different syntax for matching patterns against headers.
You can also match multiple headers in the square brackets.
.PP
.Vb 4
\& if headers["Subject","Keywords"] =~ /a bad word/ then
\& save("=swearing")
\& return
\& end
.Ve
.PP
Searches for \*(L"a bad word\*(R" in the Subject and Keywords headers, and
if it's there, saves the email in the \*(L"swearing\*(R" folder.
.PP
.Vb 4
\& if headers.matches(["To","Cc"],"mailing\-list@example.com") then
\& pipe("|rcvstore +mailing\-list")
\& return
\& end
.Ve
.PP
Any email to a mailing list is piped through \*(L"rcvstore\*(R" to store
it into an \s-1MH\s0 folder.
.PP
That
.PP
.Vb 1
\& headers.matches(["To","Cc"],/regex/)
.Ve
.PP
idiom happens often enough that there's a shorthand for it:
.PP
.Vb 4
\& if to =~ /mailing\-list@example.com/ then
\& pipe("|rcvstore +mailing\-list")
\& return
\& end
.Ve
.PP
Pipes the mail to the mailing list through \*(L"rcvstore\*(R".
.SH "ADVANCED EXAMPLES"
.IX Header "ADVANCED EXAMPLES"
Here are some slightly more clever examples to give you an idea
of what you can do with \f(CW\*(C`gurgitate\-mail\*(C'\fR. Let's suppose you have
an email whitelist in a file called \fI\f(CI$HOME\fI/.friends\fR, so you can
determine whether some email is likely to be spam or not.
.PP
Then if someone on your whitelist sends you email, then you
automatically save that into the \*(L"inbox\*(R" folder:
.PP
.Vb 10
\& friends=homedir+"/.friends"
\& if FileTest.exists?(friends) and FileTest.readable?(friends) then
\& File.new(friends).each do |friend|
\& if from =~ friend.chomp then
\& log "Mail from friend "+friend.chomp
\& save("=inbox")
\& return
\& end
\& end
\& end
.Ve
.PP
Okay, if someone sends you email, and it's addressed specifically
to you (and gurgitate-mail hasn't caught it in another form already),
then it might or might not be spam: put it into a \*(L"grey\*(R" folder:
.PP
.Vb 9
\& my_addresses= [ /me@example\e.com/i,
\& /me@example\e.org/i,
\& /me@example\e.net/i]; # I have three email addresses
\& my_addresses.each do |addr|
\& if headers.matches(["To","Cc"],addr) then
\& save("=possibly\-not\-spam")
\& return
\& end
\& end
.Ve
.PP
And after that, if it's not from someone you know, and it's not
addressed to your email address either, then it's probably save to
assume that it's spam:
.PP
.Vb 2
\& save("=spam")
\& return
.Ve
.PP
This can be improved by using a Bayesian filter, though; for
example, Eric Raymond's bogofilter program
(http://bogofilter.sourceforge.net) can be automatically trained
and used with the help of the white/grey/black distinctions.
Taking the example above, I'll adjust it by adding in calls to
bogofilter:
.PP
.Vb 11
\& friends=homedir+"/.friends"
\& if FileTest.exists?(friends) and FileTest.readable?(friends) then
\& File.new(friends).each do |friend|
\& if from =~ friend.chomp then
\& log "Mail from friend "+friend.chomp
\& pipe("bogofilter \-h") # <\-\- LINE ADDED HERE
\& save("=inbox")
\& return
\& end
\& end
\& end
.Ve
.PP
\&\f(CW\*(C`bogofilter \-h\*(C'\fR trains bogofilter that mail from whitelisted-people
is not to be considered spam. Okay, at the end of the
\&.gurgitate\-rules, change
.PP
.Vb 2
\& save("=spam")
\& return
.Ve
.PP
to
.PP
.Vb 3
\& save("=spam")
\& pipe("bogofilter \-s")
\& return
.Ve
.PP
This trains \f(CW\*(C`bogofilter\*(C'\fR that anything which doesn't pass the
rest of the filter should be considered spam. Now for the
interesting bit: Change the bit between these to use \*(L"bogofilter\*(R"
to decide whether email is to be considered spam or not:
.PP
.Vb 10
\& my_addresses= [ /me@example\e.com/i,
\& /me@example\e.org/i,
\& /me@example\e.net/i]; # I have three email addresses
\& my_addresses.each do |addr|
\& if headers.matches(["To","Cc"],addr) then
\& if pipe("bogofilter")==1
\& then
\& log("bogofilter suspects it might not be spam")
\& save("=possibly\-not\-spam")
\& else
\& log("bogofilter thinks it\*(Aqs probably spam")
\& save("=spam")
\& end
\& return
\& end
\& end
.Ve
.PP
\&\f(CW\*(C`bogofilter\*(C'\fR has an exit code of \*(L"1\*(R" if it thinks the message is
not spam, and \*(L"0\*(R" if it thinks the message is spam.
.PP
Hopefully this should give you an idea of the kinds of things that
you can use \f(CW\*(C`bogofilter\*(C'\fR for.
.SH "AUTHOR"
.IX Header "AUTHOR"
Dave Brown
gurgitate-mail/README 0000644 0000765 0000144 00000032373 10747761263 014425 0 ustar dagbrown users NAME
gurgitate-mail - an easy-to-use mail filter
SYNOPSIS
gurgitate-mail
DESCRIPTION
"gurgitate-mail" is a program which reads your mail and filters it
according to the .gurgitate-rules.rb file in your home directory. The
configuration file uses Ruby syntax and is thus quite flexible.
It's generally invoked either through your .forward file:
"|/path/to/gurgitate-mail"
Or through your .procmailrc file:
:0:
| /path/to/gurgitate-mail
Alternatively, if you're the sysadmin at your site, or your sysadmin is
friendly, you can use gurgitate-mail as a local delivery agent. For
postfix, put
mailbox_command=/opt/bin/gurgitate-mail
in /etc/postfix/main.cf. If you use any other MTA, and configure
gurgitate-mail as a local delivery agent, please tell me how! I want to
include this in the documentation.
CONFIGURATION FILES
There are three configuration files used by gurgitate-mail: two are
system-wide, and the third, is the user rules file.
The two system-wide configuration files are /etc/gurgitate-rules and
/etc/gurgitate-rules-default. These are processed before and after the
user rules, respectively.
/etc/gurgitate-rules is used to handle system-wide filtering needs:
setting the default mailbox style to Maildir rather than the default
MBox, setting the spool directory, things like that.
The user configuration file is $HOME/.gurgitate-rules (or,
alternatively, $HOME/.gurgitate-rules.rb. Either work). You put your own
rules here. If the user configuration file doesn't encounter a "return"
during processing, then the additional rules contained in
/etc/gurgitate-rules-default are run. If that also doesn't return, then
mail messages are saved into the default mail spool location.
If the "-f" option is used on the commandline, then the file specified
will be used and the default rules will not. The "-f" option can be used
more than once:
gurgitate-mail -f test-rules -f additional-rules
CONFIGURATION PARAMETERS
There are several parameters that you can set to change the way that
gurgitate-mail behaves. You set a config parameter by saying, for
instance:
sendmail "/usr/sbin/sendmail"
which sets the "sendmail" parameter to "/usr/sbin/sendmail".
maildir
The directory you want to put mail folders into. This defaults to
$HOME/Mail.
logfile
Where you went gurgitate-mail's log messages to go to. The standard
location for this is $HOME/.gurgitate.log
sendmail
The full path to the sendmail program, used to deliver mail. This
can be any program that takes as its parameters the list of
addresses to deliver mail to, and that takes a mail message on
standard input.
homedir
The full path of your home directory. This defaults to whatever
your actual home directory is.
spooldir
The path where the system's mail spools goes to. This defaults to
"/var/spool/mail". On a Maildir system, this should be set to the
same as "homedir".
spoolfile
The mail spool file component of the full path of your mail spool.
This is generally your username. Maildir users should set this to
"Maildir".
folderstyle
The style of folders you prefer. This can be (at the moment) either
MBox or Maildir.
FILTER RULES
The filter rules are a series of Ruby statements, with the following
methods and variables available:
Variables
from This contains the envelope "from" address of the email message.
(Note that this isn't necessarily the same as the contents of the
"From:" header)
headers
This is an object containing the headers of the message. There are
several methods that come with this object:
body This contains the body of the email message. As of yet, there's
nothing really interesting which you can do with this, apart from
assigning to it; you can rewrite the body of an email message this
way. Dealing with attachments is planned for a future release of
"gurgitate-mail".
maildir
The directory which contains the folders, used by the "save" method
when you specify a folder as "=folder" (like Elm). Defaults to
"$HOME/Mail".
homedir
Your home directory. Read-only.
logfile
The location of the "gurgitate-mail" logfile. If set to "nil", then
no logging is done. Defaults to "$HOME/.gurgitate.log".
sendmail
The location of the "sendmail" program. Used by the "forward"
method. Defaults to "/usr/lib/sendmail".
spoolfile
The location of the mail spool. Read-only.
Methods
matches(name(s),regex)
Returns "true" if the header "name" matches the regular expression
"regex". If "name" is an array of header names, then it returns
true if at least one of the headers matches. Useful for testing
whether both "To:" and "Cc:" headers match.
from Returns the envelope "from" address of the email message. Note that
this is the same as the bare "from".
to Returns a HeaderBag (a kind of array) with the contents of the "To"
and the "Cc" headers.
to_s As per Ruby convention, returns all the headers as a "String"
object.
save(mailbox)
This saves the message to a mailbox. You can specify the mailbox as
a word with an = sign in front of it, in which case it puts it into
"maildir". If you don't use the =name format, then you need to
specify an absolute pathname. If it can't write the message to the
file you request it to, it'll attempt to write it to "spoolfile".
forward(address)
This forwards the email message to another email address.
pipe(program)
This pipes the message through "program". "pipe" returns the exit
code of the program that the message was piped through.
filter(program)
This pipes the message through "program" and returns a new
Gurgitate object containing the filtered mail. (This is handy for
external filters which modify email like, for example,
SpamAssassin, which adds a spam-score header.)
You can also say
filter(program) do
# code here
end
and it yields the newly-created Gurgitate object to the block.
headers
This returns the headers as an object of their own. This object has
its own methods:
headers[*headernames]
This returns a HeaderBag (a subclass of array) containing the
headers you asked for. You can then use the =~ operator on
this result to match the RHS regex with everything in the
HeaderBag.
You can change a header's value with "headers[name]=newvalue".
headers.match(name,regex)
Matches the header with the name "name" against the regex.
This is the same as headers[name] =~ /regex/.
headers.matches(names,regex)
Matches the headers with the names "names" against the regex.
This is the same as headers[*names] =~ /regex/.
headers.from
Returns the envelope from. You can change this with
"headers.from=newaddress" too.
return
This tells "gurgitate-mail" to stop processing the email message.
If you don't use "return", then "gurgitate-mail" will continue
processing the same mail again with the next rule. If there isn't a
"return" at the end of gurgitate-rules.rb, then "gurgitate-mail"
will save the email message in the normal mail spool.
log(message)
This writes a log message to the log file.
SIMPLE EXAMPLES
Here are some examples of "gurgitate-mail" rules, with explanations:
if from =~ /ebay.com/ then save("=ebay"); return; end
Any email from eBay (automatic end-of-auction notifications, for
example, and outbid notices) gets filed into the "ebay" folder.
if from =~ /root@/ then save("=root"); return; end
Any email from root (at any host) gets filed into a special folder.
Useful for sysadmins monitoring crontab email.
if headers.matches(["To","Cc"],"webmaster@") then
save("=webmaster")
return
end
Any email with a To: or Cc: line of "sysadmin" is saved to a "sysadmin"
folder. Useful for people with multiple role accounts redirected to
their address.
if headers["Subject"] =~ /\[SPAM\]/ then
save("=spam")
return
end
This is a different syntax for matching patterns against headers. You
can also match multiple headers in the square brackets.
if headers["Subject","Keywords"] =~ /a bad word/ then
save("=swearing")
return
end
Searches for "a bad word" in the Subject and Keywords headers, and if
it's there, saves the email in the "swearing" folder.
if headers.matches(["To","Cc"],"mailing-list@example.com") then
pipe("|rcvstore +mailing-list")
return
end
Any email to a mailing list is piped through "rcvstore" to store it into
an MH folder.
That
headers.matches(["To","Cc"],/regex/)
idiom happens often enough that there's a shorthand for it:
if to =~ /mailing-list@example.com/ then
pipe("|rcvstore +mailing-list")
return
end
Pipes the mail to the mailing list through "rcvstore".
ADVANCED EXAMPLES
Here are some slightly more clever examples to give you an idea of what
you can do with "gurgitate-mail". Let's suppose you have an email
whitelist in a file called $HOME/.friends, so you can determine whether
some email is likely to be spam or not.
Then if someone on your whitelist sends you email, then you
automatically save that into the "inbox" folder:
friends=homedir+"/.friends"
if FileTest.exists?(friends) and FileTest.readable?(friends) then
File.new(friends).each do |friend|
if from =~ friend.chomp then
log "Mail from friend "+friend.chomp
save("=inbox")
return
end
end
end
Okay, if someone sends you email, and it's addressed specifically to you
(and gurgitate-mail hasn't caught it in another form already), then it
might or might not be spam: put it into a "grey" folder:
my_addresses= [ /me@example\.com/i,
/me@example\.org/i,
/me@example\.net/i]; # I have three email addresses
my_addresses.each do |addr|
if headers.matches(["To","Cc"],addr) then
save("=possibly-not-spam")
return
end
end
And after that, if it's not from someone you know, and it's not
addressed to your email address either, then it's probably save to
assume that it's spam:
save("=spam")
return
This can be improved by using a Bayesian filter, though; for example,
Eric Raymond's bogofilter program (http://bogofilter.sourceforge.net)
can be automatically trained and used with the help of the
white/grey/black distinctions. Taking the example above, I'll adjust it
by adding in calls to bogofilter:
friends=homedir+"/.friends"
if FileTest.exists?(friends) and FileTest.readable?(friends) then
File.new(friends).each do |friend|
if from =~ friend.chomp then
log "Mail from friend "+friend.chomp
pipe("bogofilter -h") # <-- LINE ADDED HERE
save("=inbox")
return
end
end
end
"bogofilter -h" trains bogofilter that mail from whitelisted-people is
not to be considered spam. Okay, at the end of the .gurgitate-rules,
change
save("=spam")
return
to
save("=spam")
pipe("bogofilter -s")
return
This trains "bogofilter" that anything which doesn't pass the rest of
the filter should be considered spam. Now for the interesting bit:
Change the bit between these to use "bogofilter" to decide whether email
is to be considered spam or not:
my_addresses= [ /me@example\.com/i,
/me@example\.org/i,
/me@example\.net/i]; # I have three email addresses
my_addresses.each do |addr|
if headers.matches(["To","Cc"],addr) then
if pipe("bogofilter")==1
then
log("bogofilter suspects it might not be spam")
save("=possibly-not-spam")
else
log("bogofilter thinks it's probably spam")
save("=spam")
end
return
end
end
"bogofilter" has an exit code of "1" if it thinks the message is not
spam, and "0" if it thinks the message is spam.
Hopefully this should give you an idea of the kinds of things that you
can use "bogofilter" for.
AUTHOR
Dave Brown
gurgitate-mail/gurgitate/deliver.rb 0000644 0000765 0000144 00000005643 10747761263 017517 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
# Code to handle saving a message to a mailbox (and a framework for detecting
# what kind of mailbox it is)
#------------------------------------------------------------------------
require "gurgitate/deliver/mbox"
require "gurgitate/deliver/maildir"
require "gurgitate/deliver/mh"
module Gurgitate
module Deliver
class MailboxFound < Exception
# more of a "flag" than an exception really
end
# Saves a message to +mailbox+, after detecting what the mailbox's
# format is.
# mailbox::
# A string containing the path of the mailbox to save
# the message to. If it is of the form "=mailbox", it
# saves the message to +Maildir+/+mailbox+. Otherwise,
# it simply saves the message to the file +mailbox+.
def save(mailbox)
if mailbox[0,1]=='=' and @maildir != nil then
if @folderstyle == Maildir and mailbox !~ /^=\./ then
mailbox["="]=@maildir+"/."
else
mailbox["="]=@maildir+"/"
end
end
if mailbox[0,1] != '/' then
log("Cannot save to relative filenames! Saving to spool file");
mailbox=spoolfile
end
begin
[MBox,Maildir,MH].each do |mod|
if mod::check_mailbox(mailbox) then
extend mod
raise MailboxFound
end
end
# Huh, nothing could find anything. Oh well,
# let's default to whatever's in @folderstyle. (I
# guess we'll be making a new mailbox, eh?)
if Module === @folderstyle then
#
# Careful we don't get the wrong instance variable
folderstyle=@folderstyle
extend folderstyle
else
# No hints from the user either. Let's guess!
# I'll use the same heuristic that Postfix uses--if the
# mailbox name ends with a /, then make it a Maildir,
# otherwise make it a mail file
if mailbox =~ /\/$/ then
extend Maildir
else
extend MBox
end
end
rescue MailboxFound
# Don't need to do anything--we only have to worry
# about it if there wasn't a mailbox there.
nil
end
begin
deliver_message(mailbox)
rescue SystemCallError
self.log "Gack! Something went wrong: " + $!.to_s
raise
# exit 75
end
end
end
end
gurgitate-mail/gurgitate/headers.rb 0000644 0000765 0000144 00000022726 10747761263 017501 0 ustar dagbrown users #!/opt/bin/ruby -w
require "gurgitate/header"
module Gurgitate
class IllegalHeader < RuntimeError ; end
# ========================================================================
class HeaderBag < Array
def =~(regex)
inject(false) do |y,x|
y or ( ( x =~ regex ) != nil )
end
end
def sub!(regex, replacement)
each do |header|
header.contents = header.contents.sub regex, replacement
end
end
def sub(regex, replacement)
::Gurgitate::HeaderBag.new(
self.map do |header|
::Gurgitate::Header.new(
"#{header.name}: " + header.contents.sub(regex,
replacement)
)
end
)
end
def to_s
map do |member|
member.to_s
end.join ""
end
end
# A slightly bigger class for all of a message's headers
class Headers
private
# Figures out whether the first line of a mail message is an
# mbox-style "From " line (say, if you get this from sendmail),
# or whether it's just a normal header.
# --
# If you run "fetchmail" with the -m option to feed the
# mail message straight to gurgitate, skipping the "local
# MTA" step, then it doesn't have a "From " line. So I
# have to deal with that by hand. First, check to see if
# there's a "From " line present in the first place.
def figure_out_from_line(headertext)
(unix_from,normal_headers) = headertext.split(/\n/,2)
if unix_from =~ /^From / then
headertext=normal_headers
unix_from=unix_from
else
# If there isn't, then deal with it after we've
# worried about the rest of the headers, 'cos we'll
# have to make our own.
unix_from=nil
end
return unix_from, headertext
end
def parse_headers
@headertext.each_line do |h|
h.chomp!
if(h=~/^\s+/) then
@lastheader << h
else
header=Header.new(h)
@headers[header.name] ||= HeaderBag.new
@headers[header.name].push(header)
@lastheader=header
end
end
@headers_changed=false
end
# Get the envelope From information. This comes with a
# whole category of rants: this information is absurdly hard
# to get your hands on. The best you can manage is a sort
# of educated guess. Thus, this horrible glob of hackiness.
# I don't recommend looking too closely at this code if you
# can avoid it, and further I recommend making sure to
# configure your MTA so that it sends proper sender and
# recipient information to gurgitate so that this code never
# has to be run at all.
def guess_sender
# Start by worrying about the "From foo@bar" line. If it's
# not there, then make one up from the Return-Path: header.
# If there isn't a "Return-Path:" header (then I suspect we
# have bigger problems, but still) then use From: as a wild
# guess. If I hope that this entire lot of code doesn't get
# used, then I _particularly_ hope that things never get so
# bad that poor gurgitate has to use the From: header as a
# source of authoritative information on anything.
#
# And then after all that fuss, if we're delivering to a
# Maildir, I have to get rid of it. And sometimes the MTA
# gives me a mbox-style From line and sometimes it doesn't.
# It's annoying, but I have no choice but to Just Deal With
# It.
if @unix_from then
# If it is there, then grab the email address in it and
# use that as our official "from".
fromregex=/^From ([^ ]+@[^ ]+) /
fromregex.match(@unix_from)
@from=$+
# or maybe it's local
if @from == nil then
@unix_from =~ /^From (\S+) /
@from=$+
end
else
fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+)/
if self["Return-Path"] != nil then
fromregex.match(self["Return-Path"][0].contents)
else
if self["From"] != nil then
fromregex.match(self["From"][0].contents)
end
end
address_candidate=$+
# If there STILL isn't a match, then it's probably safe to
# assume that it's local mail, and doesn't have an @ in its
# address.
unless address_candidate
if self["Return-Path"] != nil then
self["Return-Path"][0].contents =~ /(\S+)/
address_candidate=$+
else
self["From"][0].contents =~ /(\S+)/
address_candidate=$+
end
end
@from=address_candidate
@unix_from="From "+self.from+" "+Time.new.to_s
end
end
public
# Creates a Headers object.
# headertext::
# The text of the message headers.
def initialize(headertext=nil, sender=nil, recipient=nil)
@from = sender
@to = recipient
@headers = Hash.new(nil)
if Hash === headertext
@headers_changed = true
headertext.each_key do |key|
headername = key.to_s.gsub("_","-")
header=Header.new(headername, headertext[key])
@headers[header.name] ||= HeaderBag.new
@headers[header.name].push(header)
end
else
if headertext
@unix_from, @headertext = figure_out_from_line headertext
parse_headers if @headertext
if sender # then don't believe the mbox separator
@from = sender
@unix_from="From "+self.from+" "+Time.new.to_s
else
guess_sender
end
end
end
end
# Grab the headers with names +names+
# names:: The names of the header.
def [](*names)
if names.inject(false) do |accum,name|
accum or @headers.has_key? name
end then
return HeaderBag.new(names.collect { |name|
@headers[name]
}.flatten.delete_if { |e| e == nil } )
else
return nil
end
end
# Set the header named +name+ to +value+
# name:: The name of the header.
# value:: The new value of the header.
def []=(name,value)
@headers_changed = true
@headers[name]=HeaderBag.new([Header.new(name,value)])
end
# Who the message is to (the envelope to)
#
# Yet another bucket of rants. Unix mail sucks.
def to
return @to || @headers["X-Original-To"] || nil
end
# Who the message is from (the envelope from)
def from
return @from || ""
end
# Change the envelope from line to whatever you want. This might
# not be particularly neighborly, but oh well.
# newfrom:: An email address
def from=(newfrom)
@from=newfrom
@unix_from="From "+self.from+" "+Time.new.to_s
end
# Match header +name+ against +regex+
# name::
# A string containing the name of the header to match (for example,
# "From")
# regex:: The regex to match it against (for example, /@aol.com/)
def match(name,regex)
ret=false
if(@headers[name]) then
@headers[name].each do |h|
ret |= h.matches(regex)
end
end
return ret
end
# Return true if headers +names+ match +regex+
# names:: An array of header names (for example, %w{From Reply-To})
# regex:: The regex to match the headers against.
def matches(names,regex)
ret=false
if names.class == String then
names=[names]
end
names.each do |n|
ret |= match(n,regex)
end
return ret
end
# Returns the headers properly formatted for an email
# message.
def to_mbox
return @unix_from+"\n"+to_s
end
# Returns the headers formatted for an email message (without
# the "From " line
def to_s
if @headers_changed then
return @headers.map do |name,hdr|
hdr.map do |hdr_content| hdr_content.to_s end.join("\n")
end.join("\n")
else
return @headertext
end
end
end
end
gurgitate-mail/gurgitate/header.rb 0000644 0000765 0000144 00000004712 10747761263 017311 0 ustar dagbrown users #!/opt/bin/ruby -w
module Gurgitate
class IllegalHeader < RuntimeError ; end
# A little class for a single header
class Header
# The name of the header
attr_accessor :name
# The contents of the header
attr_accessor :contents
alias_method :value, :contents
# A recent rash of viruses has forced me to canonicalize
# the capitalization of headers. Sigh.
def capitalize_words(s)
return s.split(/-/).map { |w| w.capitalize }.join("-")
rescue
return s
end
private :capitalize_words
# Creates a Header object.
# header::
# The text of the email-message header
def initialize(*header)
name,contents=nil,nil
if header.length == 1 then
# RFC822 says that a header consists of some (printable,
# non-whitespace) crap, followed by a colon, followed by
# some more (printable, but can include whitespaces)
# crap.
if(header[0] =~ /^[\x21-\x39\x3b-\x7e]+:/) then
(name,contents)=header[0].split(/:\s+/,2)
if(name =~ /:$/ and contents == nil) then
# It looks like someone is using Becky!
name=header[0].gsub(/:$/,"")
contents = ""
end
raise IllegalHeader, "Empty name" \
if (name == "" or name == nil)
contents="" if contents == nil
@@lastname=name
else
raise IllegalHeader, "Bad header syntax: no colon in #{header}"
end
elsif header.length == 2 then
name,contents = *header
end
@name=capitalize_words(name)
@contents=contents
end
# Extended header
def << (text)
@contents += "\n" + text
end
# Matches a header's contents.
# regex::
# The regular expression to match against the header's contents
def matches (regex)
if String === regex
regex = Regexp.new(Regexp.escape(regex))
end
@contents =~ regex
end
alias :=~ :matches
# Returns the header, ready to put into an email message
def to_s
@name+": "+@contents
end
end
end
gurgitate-mail/gurgitate/mailmessage.rb 0000644 0000765 0000144 00000005174 10747761263 020353 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
# Handles a complete mail message
#------------------------------------------------------------------------
require 'gurgitate/headers'
module Gurgitate
# A complete mail message.
class Mailmessage
Fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<][<](.*@.*)[>]|([^ ]+@[^ ]+)/;
# The headers of the message
attr_reader :headers
# The body of the message
attr_accessor :body
# The envelope sender and recipient, if anyone thought to
# mention them to us.
attr_accessor :sender
attr_accessor :recipient
# Creates a new mail message with headers built from the options hash,
# and the body of the message in a string.
def self.create(*args)
options = body = nil
if String === args[0]
options = args[1]
body = args[0]
elsif Hash === args[0]
options = args[0]
else
options = {}
end
message = self.new
message.instance_eval do
if body
@body=body
end
%w/sender recipient body/.each do |key|
if options.has_key? key.to_sym
instance_variable_set("@#{key}", options[key.to_sym])
options.delete key.to_sym
end
end
@headers = Headers.new(options)
end
message
end
def initialize(text=nil, recipient=nil, sender=nil)
@recipient = recipient
@sender = sender
if text
(@headertext,@body)=text.split(/\n\n/,2)
@headers=Headers.new(@headertext);
Fromregex.match(@headers["From"][0].contents);
@from=$+
else
@headers = Headers.new
@body = ""
end
end
# Returns the header +name+
def header(name)
@headers[name].each { |h| h.contents }.join(", ")
end
# custom accessors
# Returns the message's sender
def from; @sender || @headers.from; end
# Returns all the candidates for a recipient
def to; @recipient || @headers["To", "Cc"][0].contents; end
# Returns the formatted mail message
def to_s; @headers.to_s + "\n\n" + ( @body || ""); end
# Returns the mail message formatted for mbox
def to_mbox; @headers.to_mbox + "\n\n" + @body; end
end
end
gurgitate-mail/gurgitate/deliver/maildir.rb 0000644 0000765 0000144 00000010146 10747761263 021132 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
# Deliver mail to a maildir (also, detects maildir)
#------------------------------------------------------------------------
require "socket" # for gethostname (!)
module Gurgitate
module Deliver
module Maildir
# Figures out if +mailbox+ is a Maildir mailbox
# mailbox::
# A string containing the path of the mailbox to save the
# message to. If it is of the form "=mailbox", it saves
# the message to +Maildir+/+mailbox+. Otherwise, it
# simply saves the message to the file +mailbox+.
def self::check_mailbox(mailbox)
begin
if File.stat(mailbox).directory? then
if File.stat(File.join(mailbox,"cur")).directory? then
return Maildir
end
end
rescue Errno::ENOENT
return nil
end
end
# Figures out the first available filename in the mail dir
# +dir+ and returns the filename to use.
# dir::
# One of "+mailbox+/tmp" or "+mailbox+/new", but that's
# only because that's what the maildir spec
# (http://cr.yp.to/proto/maildir.html) says.
def maildir_getfilename(dir)
time=Time.now.to_f
counter=0
hostname=Socket::gethostname
filename=nil
loop do
filename=File.join(dir,sprintf("%.4f.%d_%d.%s",
time,$$,counter,hostname))
break if not File.exists?(filename)
counter+=1
end
return filename
end
# Creates a new Maildir folder +mailbox+
# mailbox::
# The full path of the new folder to be created
def make_mailbox(mailbox)
Dir.mkdir(mailbox)
%w{cur tmp new}.each do |dir|
Dir.mkdir(File.join(mailbox,dir))
end
end
# Delivers a message to the maildir-format mailbox +mailbox+.
# mailbox::
# A string containing the path of the mailbox to save the
# message to. If it is of the form "=mailbox", it saves
# the message to +Maildir+/+mailbox+. Otherwise, it
# simply saves the message to the file +mailbox+.
def deliver_message(mailbox)
begin
File.stat(mailbox)
rescue Errno::ENOENT
make_mailbox(mailbox)
end
unless File.stat(mailbox).directory?
raise SystemError, 'not a directory'
end
tmpfilename=maildir_getfilename(File.join(mailbox,"tmp"))
File.open(tmpfilename,File::CREAT|File::WRONLY) do |fh|
fh.write(self.to_s)
fh.flush
# I should put a caveat here, unfortunately. Ruby's
# IO#flush only flushes Ruby's buffers, not the
# operating system's. If anyone knows how to force
# a real fflush(), I'd love to know. Otherwise, I'm
# going to hope that closing the file does the trick
# for me.
end
# ...and link to new.
# (I guess Maildir mailboxes don't work too well
# on Windows, eh?)
newfilename = maildir_getfilename(
File.join(mailbox,"new"))
begin
File.link(tmpfilename,newfilename)
rescue SystemCallError
log("Couldn't create maildir link to \"new\"!")
exit 75 # Argh, I tried, it didn't work out
end
File.delete(tmpfilename)
end
end
end
end
gurgitate-mail/gurgitate/deliver/mbox.rb 0000644 0000765 0000144 00000003404 10747761264 020456 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
# Delivers a message to an mbox (also includes mbox detector)
#------------------------------------------------------------------------
module Gurgitate
module Deliver
module MBox
# Checks to see if +mailbox+ is an mbox mailbox
# mailbox::
# A string containing the path of the mailbox to save
# the message to. If it is of the form "=mailbox", it
# saves the message to +Maildir+/+mailbox+. Otherwise,
# it simply saves the message to the file +mailbox+.
def self::check_mailbox(mailbox)
begin
if File.stat(mailbox).file? then
return MBox
else
return nil
end
rescue Errno::ENOENT
return nil
end
end
# Delivers the message to +mailbox+
# mailbox::
# A string containing the path of the mailbox to save
# the message to. If it is of the form "=mailbox", it
# saves the message to +Maildir+/+mailbox+. Otherwise,
# it simply saves the message to the file +mailbox+.
def deliver_message(mailbox)
File.open(mailbox,File::WRONLY |
File::APPEND |
File::CREAT) do |f|
f.flock(File::LOCK_EX)
message=(if f.stat.size > 0 then "\n" else "" end) + to_mbox
f.print message
f.flock(File::LOCK_UN)
end
end
end
end
end
gurgitate-mail/gurgitate/deliver/mh.rb 0000644 0000765 0000144 00000012322 10747761264 020114 0 ustar dagbrown users #!/opt/bin/ruby -w
require "yaml"
#------------------------------------------------------------------------
# Delivers a message to an mbox (also includes mbox detector)
#------------------------------------------------------------------------
module Gurgitate
module Deliver
module MH
# Checks to see if +mailbox+ is an mbox mailbox
# mailbox::
# A string containing the path of the mailbox to save
# the message to. If it is of the form "=mailbox", it
# saves the message to +Maildir+/+mailbox+. Otherwise,
# it simply saves the message to the file +mailbox+.
def self::check_mailbox(mailbox)
begin
# Rather annoyingly, pretty well any directory can
# be a MH mailbox, but this just checks to make sure
# it's not actually a Maildir by mistake.
#
# I could put in a check for the path given in
# $HOME/.mh_profile, but Claws-Mail uses MH mailboxes and
# disregards $HOME/.mh_profile.
if File.stat(mailbox).directory? and
not ( File.exists?(File.join(mailbox, "cur")) or
File.exists?(File.join(mailbox, "tmp")) or
File.exists?(File.join(mailbox, "new"))) then
return MH
end
rescue Errno::ENOENT
return nil
end
end
# Delivers the message to +mailbox+
# mailbox::
# A string containing the path of the mailbox to save
# the message to. If it is of the form "=mailbox", it
# saves the message to +Maildir+/+mailbox+. Otherwise,
# it simply saves the message to the file +mailbox+.
def deliver_message(mailbox)
if ! File.exists? mailbox then
Dir.mkdir(mailbox)
end
if File.exists? mailbox and not File.directory? mailbox then
raise SystemError, "not a directory"
end
new_msgnum = next_message(mailbox) do |filehandle|
filehandle.print self.to_s
end
update_sequences(mailbox, new_msgnum)
end
private
def update_sequences(mailbox, msgnum)
sequences = File.join(mailbox, ".mh_sequences")
lockfile = sequences + ".lock" # how quaint
loop do
begin
File.open(lockfile,
File::WRONLY |
File::CREAT |
File::EXCL ) do |lock|
File.open(sequences,
File::RDWR | File::CREAT) do |seq|
seq.flock(File::LOCK_EX)
metadata = YAML.load(seq.read) || Hash.new
metadata["unseen"] = update_unseen \
metadata["unseen"], msgnum
seq.rewind
metadata.each do |key, val|
seq.puts "#{key}: #{val}"
end
seq.truncate seq.tell
seq.flock(File::LOCK_UN)
end
end
File.unlink(lockfile)
break
rescue Errno::EEXIST
# some other process is doing something, so wait a few
# milliseconds until it's done
sleep(0.01)
end
end
end
def update_unseen unseen, msgnum
prevmsg = msgnum - 1
if unseen
unseenstring = unseen.to_s
if unseenstring =~ /-#{prevmsg}/ then
return unseenstring.sub(/\b#{prevmsg}\b/, msgnum.to_s)
end
if unseenstring.match(/\b#{prevmsg}\b/) then
return "#{unseenstring}-#{msgnum}"
end
return "#{unseenstring} #{msgnum}"
else
return msgnum
end
end
def next_message(mailbox)
next_msgnum = Dir.open(mailbox).map { |ent| ent.to_i }.max + 1
loop do
begin
File.open(File.join(mailbox, next_msgnum.to_s),
File::WRONLY |
File::CREAT |
File::EXCL ) do |filehandle|
yield filehandle
end
break
rescue Errno::EEXIST
next_msgnum += 1
end
end
return next_msgnum
end
end
end
end
gurgitate-mail/test/gurgitate-test.rb 0000644 0000765 0000144 00000002666 10747014532 020011 0 ustar dagbrown users require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
require 'fileutils'
require 'pathname'
require 'irb'
$:.unshift File.dirname(__FILE__) + "/.."
require "gurgitate-mail"
class GurgitateTest < Test::Unit::TestCase
def setup
currentdir = Pathname.new(File.join(File.dirname(__FILE__),
"..")).realpath.to_s
@testdir = File.join(currentdir,"test-data")
@folders = File.join(@testdir,"folders")
FileUtils.rmtree @testdir if File.exists? @testdir
Dir.mkdir @testdir
Dir.mkdir @folders
m = StringIO.new("From: me\nTo: you\nSubject: test\n\nHi.\n")
@gurgitate = nil
@gurgitate = Gurgitate::Gurgitate.new(m)
testdir = @testdir
folders = @folders
@gurgitate.instance_eval do
sendmail "/bin/cat"
homedir testdir
spooldir testdir
spoolfile File.join(testdir, "default")
maildir folders
end
@spoolfile = File.join(testdir, "default")
end
def maildirmake mailbox # per the UNIX command
FileUtils.mkdir mailbox
%w/cur tmp new/.each do |subdir|
FileUtils.mkdir File.join(mailbox, subdir)
end
end
def mhdirmake mailbox # per "maildirmake"
FileUtils.mkdir mailbox
end
def teardown
FileUtils.rmtree @testdir
end
def test_truth
assert true
end
end
gurgitate-mail/test/runtests.rb 0000644 0000765 0000144 00000001345 10727646633 016734 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
# Unit tests for gurgitate-mail
#------------------------------------------------------------------------
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
require 'pathname'
def runtests(testcases)
testcases.each do |testcase|
Test::Unit::UI::Console::TestRunner.run testcase
end
end
testpath = Pathname.new(__FILE__).dirname.realpath
testcases = Dir[File.join(testpath,"test_*")].map do |file|
load file
eval("TC_" + File.basename(file,".rb").sub(/^test_/,'').capitalize)
end
if __FILE__ == $0 then
if(ARGV[0] == '-c')
require 'coverage'
end
runtests testcases
end
gurgitate-mail/test/test_configuration.rb 0000644 0000765 0000144 00000002167 10727646633 020756 0 ustar dagbrown users require "test/gurgitate-test"
require "etc"
class TC_Configuration < GurgitateTest
def test_default_configuration
assert_equal Gurgitate::Deliver::MBox, @gurgitate.folderstyle
assert_equal @folders, @gurgitate.maildir
assert_equal @spoolfile, @gurgitate.spoolfile
assert_equal @testdir, @gurgitate.homedir
end
def test_changing_folderstyle_to_maildir
assert_nothing_raised do
@gurgitate.folderstyle Gurgitate::Deliver::Maildir
end
assert_equal File.join(@testdir,"Maildir"), @gurgitate.spoolfile
assert_equal File.join(@testdir,"Maildir"), @gurgitate.maildir
end
def test_changing_folderstyle_to_mbox
assert_nothing_raised do
@gurgitate.folderstyle Gurgitate::Deliver::MBox
end
assert_equal "/var/spool/mail", @gurgitate.spooldir
assert_equal File.join("/var/spool/mail",
Etc.getpwuid.name), @gurgitate.spoolfile
end
def test_illegal_folderstyle_syntax
assert_raises ArgumentError do
@gurgitate.folderstyle 1, 2, 3
end
end
end
gurgitate-mail/test/test_deliver.rb 0000644 0000765 0000144 00000005447 10747014142 017527 0 ustar dagbrown users require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
require 'fileutils'
require 'pathname'
require 'irb'
require "test/gurgitate-test"
require "gurgitate/deliver"
class DeliverTest
include Gurgitate::Deliver
attr_accessor :folderstyle
def to_mbox
"From test\n" + to_s
end
def to_s
"From: test\nTo:test\nSubject: test\n\nTest\n"
end
end
class TC_Deliver < Test::Unit::TestCase
def setup
currentdir = Pathname.new(File.join(File.dirname(__FILE__),
"..")).realpath.to_s
@testdir = File.join(currentdir,"test-data")
@folders = File.join(@testdir,"folders")
FileUtils.rmtree @testdir if File.exists? @testdir
Dir.mkdir @testdir
Dir.mkdir @folders
m = StringIO.new("From: me\nTo: you\nSubject: test\n\nHi.\n")
@deliver_test = DeliverTest.new
testdir = @testdir
folders = @folders
@deliver_test.folderstyle = Gurgitate::Deliver::MBox
@spoolfile = File.join(testdir, "default")
end
def teardown
FileUtils.rmtree @testdir
end
# ------------------------------------------------------------------------
# And the tests
# ------------------------------------------------------------------------
def test_setup_worked
assert true
end
def test_basic_delivery
assert_nothing_raised do
@deliver_test.save(@spoolfile)
end
assert File.exists?(@spoolfile)
assert File.file?(@spoolfile)
assert_equal File.read(@spoolfile), @deliver_test.to_mbox
end
def test_basic_delivery_maildir
@deliver_test.folderstyle = Gurgitate::Deliver::Maildir
assert_nothing_raised do
@deliver_test.save(@spoolfile)
end
assert File.exists?(@spoolfile)
assert File.directory?(@spoolfile)
assert File.exists?(File.join(@spoolfile,"cur"))
assert File.exists?(File.join(@spoolfile,"new"))
assert File.exists?(File.join(@spoolfile,"tmp"))
contents = Dir[File.join(@spoolfile,"new","*")]
assert contents.length == 1
assert File.exists?(contents[0])
assert_equal File.read(contents[0]), @deliver_test.to_s
end
def test_basic_delivery_mh
@deliver_test.folderstyle = Gurgitate::Deliver::MH
assert_nothing_raised do
@deliver_test.save(@spoolfile)
end
assert File.exists?(@spoolfile)
assert File.directory?(@spoolfile)
mess = File.join(@spoolfile,"1")
seq = File.join(@spoolfile,".mh_sequences")
assert File.exists?(mess)
assert File.exists?(seq)
assert_equal File.read(mess), @deliver_test.to_s
assert_equal File.read(seq), "unseen: 1\n"
end
end
gurgitate-mail/test/test_delivery.rb 0000644 0000765 0000144 00000015034 10734403267 017717 0 ustar dagbrown users require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
require 'fileutils'
require 'pathname'
require 'irb'
require "test/gurgitate-test"
require "gurgitate-mail"
class TC_Delivery < GurgitateTest
#************************************************************************
# tests
#************************************************************************
def test_basic_delivery
assert_nothing_raised do
@gurgitate.process { nil }
end
assert File.exists?(@spoolfile)
end
def test_detect_mbox
Dir.mkdir(@testdir) rescue nil
File.open(@spoolfile, File::WRONLY | File::CREAT) do |f|
f.print ""
end
assert_nothing_raised do
@gurgitate.process { nil }
end
assert File.exists?(@spoolfile)
end
def test_detect_maildir
maildirmake @spoolfile
assert_nothing_raised do
@gurgitate.process { nil }
end
assert Dir[File.join(@spoolfile,"new","*")].length > 0
assert File.exists?(Dir[File.join(@spoolfile,"new","*")][0])
FileUtils.rmtree @spoolfile
teardown
test_detect_mbox
setup
end
def test_save_folders
assert_nothing_raised do
@gurgitate.process do
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).file?
end
def test_save_guess_maildir
maildirmake File.join(@folders,"test")
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert_equal 0, Dir[File.join(@folders, "test", "new", "*")].length
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_nothing_raised do
@gurgitate.process do
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert File.stat(File.join(@folders, "test","new")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_equal 1, Dir[File.join(@folders, "test", "new", "*")].length
end
def test_save_maildir_collision
maildirmake File.join(@folders,"test")
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert_equal 0, Dir[File.join(@folders, "test", "new", "*")].length
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_nothing_raised do
@gurgitate.process do
save "=test"
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert File.stat(File.join(@folders, "test","new")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_equal 2, Dir[File.join(@folders, "test", "new", "*")].length
end
def test_save_create_maildir
maildirmake @spoolfile
assert_nothing_raised do
@gurgitate.process do
@folderstyle = Gurgitate::Deliver::Maildir
@maildir = @spoolfile
save "=test"
break
end
end
assert File.exists?(File.join(@spoolfile, ".test"))
assert File.stat(File.join(@spoolfile, ".test")).directory?
assert File.exists?(File.join(@spoolfile, ".test", "new"))
assert File.stat(File.join(@spoolfile, ".test","new")).directory?
assert_equal 0, Dir[File.join(@spoolfile, ".test", "cur", "*")].length
assert_equal 1, Dir[File.join(@spoolfile, ".test", "new", "*")].length
end
def test_save_bad_filename
assert_nothing_raised do
@gurgitate.process do
save "testing"
end
end
assert File.exists?(@spoolfile)
assert !File.exists?("testing")
end
def test_cannot_save
FileUtils.touch @spoolfile
FileUtils.chmod 0, @spoolfile
assert_raises Errno::EACCES do
@gurgitate.process do
nil
end
end
end
def test_mailbox_heuristics_mbox
@gurgitate.instance_eval do
@folderstyle = nil
end
assert_nothing_raised do
@gurgitate.process do
save "=test"
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).file?
end
def test_mailbox_heuristics_maildir
@gurgitate.instance_eval do
@folderstyle = nil
end
assert_nothing_raised do
@gurgitate.process do
save "=test/"
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert File.stat(File.join(@folders, "test","new")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_equal 1, Dir[File.join(@folders, "test", "new", "*")].length
end
def test_message_parsed_correctly
assert_equal("From: me",@gurgitate.header("From"))
assert_equal("To: you", @gurgitate.header("To"))
assert_equal("Subject: test", @gurgitate.header("Subject"))
assert_equal("Hi.\n", @gurgitate.body, "Message body is wrong")
end
def test_message_written_correctly
test_message_parsed_correctly
assert_nothing_raised do
@gurgitate.process
end
mess=nil
assert_nothing_raised do
mess = Gurgitate::Mailmessage.new(File.read(@spoolfile))
end
assert_equal("From: me", mess.header("From"), "From header is wrong")
assert_equal("To: you", mess.header("To"), "To header is wrong")
assert_equal("Hi.\n", mess.body, "Body is wrong")
assert_equal("Subject: test", mess.header("Subject"), "Subject header wrong")
end
end
gurgitate-mail/test/test_header.rb 0000644 0000765 0000144 00000013440 10734403270 017315 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
#
#------------------------------------------------------------------------
builddir = File.join(File.dirname(__FILE__),"..")
unless $:[0] == builddir
$:.unshift builddir
end
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
class TC_Header < Test::Unit::TestCase
def setup
require 'gurgitate-mail'
end
def test_simple_header
h=Gurgitate::Header.new("From: fromheader@example.com")
assert_equal(h.name,"From", "Simple header name is From")
assert_equal(h.contents,"fromheader@example.com",
"Contents is fromheader@example.com")
assert_equal(h.value,"fromheader@example.com",
"Contents is fromheader@example.com")
end
def test_canonicalize_crashing
String.class_eval do
alias old_capitalize capitalize
def capitalize
raise RuntimeError
end
end
test_simple_header
String.class_eval do
alias capitalize old_capitalize
end
end
# This is an illegal header that turns up in spam sometimes.
# Crashing when you get spam is bad.
def test_malcapitalized_header
h=Gurgitate::Header.new("FROM: fromheader@example.com")
assert_equal(h.name,"From", "Badly-capitalized header is From")
assert_equal(h.contents,"fromheader@example.com",
"Badly-capitalized header")
assert_equal(h.value,"fromheader@example.com",
"Badly-capitalized header")
end
# I got a message with "X-Qmail-Scanner-1.19" once. I hate whoever did
# that.
def test_dot_in_header
h=Gurgitate::Header.new("From.Header: fromheader@example.com")
assert_equal(h.name, "From.header",
"header with dot in it is From.header")
assert_equal(h.contents, "fromheader@example.com",
"header with dot in it")
assert_equal(h.value, "fromheader@example.com",
"header with dot in it")
end
# Dammit! My new "anything goes" header parser was parsing too much
def test_delivered_to
h=Gurgitate::Header.new("Delivered-To: dagbrown@example.com")
assert_equal("Delivered-To", h.name)
assert_equal "dagbrown@example.com", h.contents
assert_equal "dagbrown@example.com", h.value
end
# This is another particularly horrible spamware-generated not-a-header.
def test_header_that_starts_with_hyphen
h=Gurgitate::Header.new("-From: -fromheader@example.com")
assert_equal(h.name, "-From",
"header with leading hyphen is -From")
assert_equal(h.contents, "-fromheader@example.com",
"header with leading hyphen")
assert_equal(h.value, "-fromheader@example.com",
"header with leading hyphen")
end
# This is another illegal header that turns up in spam sometimes.
# Crashing when you get spam is bad.
def test_nonalphabetic_initial_char_header
h=Gurgitate::Header.new("2From: fromheader@example.com")
assert_equal(h.name,"2from",
"Header that starts with illegal char is 2From")
assert_equal(h.contents, "fromheader@example.com",
"Header that starts with illegal char")
assert_equal(h.value, "fromheader@example.com",
"Header that starts with illegal char")
end
def test_bad_headers
assert_raises(Gurgitate::IllegalHeader,"Empty name") {
h=Gurgitate::Header.new(": Hi")
}
# assert_raises(Gurgitate::IllegalHeader,"Empty header contents") {
# h=Gurgitate::Header.new("From: ")
# }
assert_raises(Gurgitate::IllegalHeader,"Bad header syntax") {
h=Gurgitate::Header.new("This is completely wrong")
}
end
def test_extending_header
h=Gurgitate::Header.new("From: fromheader@example.com")
h << " (Dave Brown)"
assert_equal(h.name,"From","Extended header is From")
assert_equal(h.contents,"fromheader@example.com\n (Dave Brown)",
"Extended header contains all data")
assert_equal(h.contents,h.value,"Contents same as value")
end
def test_empty_header_with_extension
h=Gurgitate::Header.new("From:")
h << " fromheader@example.com"
assert_equal("From",h.name,"Empty extended header is From")
assert_equal("\n fromheader@example.com", h.contents,
"Empty extended header contains all data")
assert_equal(h.contents, h.value, "Contents same as value")
end
def test_changing_header
h=Gurgitate::Header.new("From: fromheader@example.com")
h.contents="anotherfromheader@example.com"
assert_equal(h.contents,"anotherfromheader@example.com",
"header contents contains new data")
end
def test_tabseparated_header
h=Gurgitate::Header.new("From:\tfromheader@example.com")
assert_equal(h.name,"From","Tabseparated header is From (bug#154)")
assert_equal(h.contents,"fromheader@example.com",
"Tabseparated header's contents are correct (bug#154)")
assert_equal(h.contents,h.value,"Contents same as value (bug#154)")
end
def test_conversion_to_String
h=Gurgitate::Header.new("From: fromheader@example.com")
assert_equal(h.to_s,"From: fromheader@example.com", "Conversion to string returns input")
end
def test_regex_match
h=Gurgitate::Header.new("From: fromheader@example.com")
assert_equal(0,h.matches(/fromheader/),"Matches regex that would match input")
assert_equal(nil,h.matches(/notininput/),"Does not match regex that would not match input")
end
end
gurgitate-mail/test/test_process.rb 0000644 0000765 0000144 00000004413 10734403270 017543 0 ustar dagbrown users builddir = File.join(File.dirname(__FILE__),"..")
unless $:[0] == builddir
$:.unshift builddir
end
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
require 'fileutils'
require 'pathname'
require 'irb'
require "gurgitate-mail"
require "test/gurgitate-test"
class TC_Process < GurgitateTest
#************************************************************************
# tests
#************************************************************************
def test_basic_delivery
assert_nothing_raised do
@gurgitate.process { nil }
end
assert File.exists?(@spoolfile)
end
def test_pipe_raises_no_exceptions
assert_nothing_raised do
@gurgitate.process { pipe('cat > /dev/null') }
end
end
def test_break_does_not_deliver
assert_nothing_raised do
@gurgitate.process { break }
end
assert !File.exists?(@spoolfile)
end
def test_message_parsed_correctly
assert_equal("From: me",@gurgitate.header("From"))
assert_equal("To: you", @gurgitate.header("To"))
assert_equal("Subject: test", @gurgitate.header("Subject"))
assert_equal("Hi.\n", @gurgitate.body, "Message body is wrong")
end
def test_message_written_correctly
test_message_parsed_correctly
assert_nothing_raised do
@gurgitate.process
end
mess=nil
assert_nothing_raised do
mess = Gurgitate::Mailmessage.new(File.read(@spoolfile))
end
assert_equal("From: me", mess.header("From"), "From header is wrong")
assert_equal("To: you", mess.header("To"), "To header is wrong")
assert_equal("Hi.\n", mess.body, "Body is wrong")
assert_equal("Subject: test", mess.header("Subject"), "Subject header wrong")
end
def test_method_missing
assert_equal "test", @gurgitate.subject[0].contents
assert_raises NameError do
p @gurgitate.nonexistentheader
end
end
def test_filter
new_obj = nil
assert_nothing_raised do
new_obj = @gurgitate.filter("tr a-z A-Z")
end
assert_equal "HI.\n", new_obj.body
assert_equal "ME", new_obj.from
assert_equal "TEST", new_obj.subject[0].contents
end
end
gurgitate-mail/test/test_rules.rb 0000644 0000765 0000144 00000005415 10727646634 017241 0 ustar dagbrown users require "test/gurgitate-test"
require "etc"
class TC_Rules < GurgitateTest
def setup
super
@rulesfile = File.join(@testdir, "rules_1.rb")
File.open(rulesfile, File::CREAT|File::WRONLY) do |f|
f.puts "nil"
end
@defaultrules = File.join(@testdir, ".gurgitate-rules.rb")
File.open File.join(@defaultrules), File::CREAT|File::WRONLY do |f|
f.puts "nil"
end
end
attr_reader :rulesfile
def teardown
File.unlink rulesfile if File.exist? rulesfile
end
def test_add_rules_normal
assert @gurgitate.add_rules(rulesfile)
assert_equal [rulesfile], @gurgitate.instance_variable_get("@rules")
end
def test_add_unreadable_rules
FileUtils.chmod 0, rulesfile
assert_equal false, @gurgitate.add_rules(rulesfile)
end
def test_add_nonexistent_rules
File.unlink rulesfile
assert_equal false, @gurgitate.add_rules(rulesfile)
end
def test_add_default_rules
assert_nothing_raised do
@gurgitate.add_rules :default
end
assert_equal [@defaultrules],
@gurgitate.instance_variable_get("@rules")
end
def test_add_system_rules
unless Etc.getpwuid.uid == 0
assert_equal false, @gurgitate.add_rules(rulesfile, :system => true)
else
# but really, fer crying out loud, DON'T RUN TESTS AS ROOT
assert true, @gurgitate.add_rules(rulesfile, :system => true)
end
end
def test_add_user_rules
assert @gurgitate.add_rules(rulesfile, :user => true)
end
def test_add_user_rules_file_not_found
File.unlink rulesfile
assert_equal false, @gurgitate.add_rules(rulesfile, :user => true)
end
def test_bad_syntax
assert_raises ArgumentError do
@gurgitate.add_rules(rulesfile, :honk)
end
end
end
class TC_ExecuteRules < TC_Rules
def setup
super
@invalidrules = File.join(@testdir, "rules_invalid.rb")
File.open @invalidrules, "w" do |f|
f.puts "invalid syntax"
end
@exceptionrules = File.join(@testdir, "rules_exception.rb")
File.open @exceptionrules, "w" do |f|
f.puts "raise RuntimeError, 'testing'"
end
end
def teardown
File.unlink @invalidrules if File.exists? @invalidrules
end
def test_process_default
@gurgitate.add_rules :default
assert_nothing_raised do
@gurgitate.process
end
end
def test_process_rules_not_found
@gurgitate.add_rules @rulesfile
File.unlink @rulesfile
assert_nothing_raised do
@gurgitate.process
end
assert File.exists?(@spoolfile)
end
end
gurgitate-mail/test/test_writing.rb 0000644 0000765 0000144 00000007755 10734403270 017564 0 ustar dagbrown users #/opt/bin/ruby -w
builddir = File.join(File.dirname(__FILE__),"..")
unless $:[0] == builddir
$:.unshift builddir
end
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
class TC_Writing < Test::Unit::TestCase
def setup
require 'gurgitate-mail'
end
def test_null_message
assert_nothing_raised do
Gurgitate::Mailmessage.new
end
end
def test_one_header_message
mess = Gurgitate::Mailmessage.new
mess.headers["From"] = "test@test"
assert_equal "From: test@test", mess.headers.to_s
assert_equal "From: test@test\n\n", mess.to_s
end
def test_two_headers_message
mess = Gurgitate::Mailmessage.new
mess.headers["From"] = "test@test"
mess.headers["To"] = "test2@test2"
assert_equal "From: test@test\nTo: test2@test2", mess.headers.to_s
end
def test_initialization_headers_only
mess = Gurgitate::Mailmessage.create :from => "test@test", :to => "test2@test2"
assert_equal [ "From: test@test", "To: test2@test2" ],
mess.headers.to_s.split(/\n/)
end
def test_initialization_headers_and_body
mess = Gurgitate::Mailmessage.create "This is a test",
:from => "test@test",
:to => "test2@test"
assert_equal "This is a test", mess.body
assert_equal [ "From: test@test", "To: test2@test" ],
mess.headers.to_s.split(/\n/)
end
def test_initialization_headers_body_in_initialization_hash
mess = Gurgitate::Mailmessage.create :body => "This is a test",
:from => "test@test",
:to => "test2@test"
assert_equal "This is a test", mess.body
assert_equal [ "From: test@test", "To: test2@test" ],
mess.headers.to_s.split(/\n/)
end
def test_creation_round_trip
mess = Gurgitate::Mailmessage.create "This is a test",
:from => "test@test",
:to => "test2@test2",
:subject => "Test subject"
reparsed_mess = Gurgitate::Mailmessage.new(mess.to_s)
assert_equal reparsed_mess.to_s, mess.to_s
end
def test_creation_sender_specified
mess = Gurgitate::Mailmessage.create :body => "This is a test",
:from => "from@test",
:to => "to@test",
:sender => "sender@test"
assert_equal "This is a test", mess.body
assert_equal "From: from@test", mess.headers["From"].to_s
assert_equal "To: to@test", mess.headers["To"].to_s
assert_equal "sender@test", mess.from
end
def test_creation_recipient_specified
mess = Gurgitate::Mailmessage.create :body => "This is a test",
:from => "from@test",
:to => "to@test",
:recipient => "recipient@test"
assert_equal "This is a test", mess.body
assert_equal "From: from@test", mess.headers["From"].to_s
assert_equal "To: to@test", mess.headers["To"].to_s
assert_equal "recipient@test", mess.to
end
def test_creation_sender_not_specified
mess = Gurgitate::Mailmessage.create :body => "This is a test",
:from => "from@test",
:to => "to@test"
assert_equal "This is a test", mess.body
assert_equal "From: from@test", mess.headers["From"].to_s
assert_equal "To: to@test", mess.headers["To"].to_s
assert_equal "", mess.from
assert_equal "to@test", mess.to
end
def test_creation_incrementally
mess = Gurgitate::Mailmessage.create
mess.sender = "sender@test"
mess.recipient = "recipient@test"
mess.body = "This is a test"
mess.headers["From"] = "from@test"
mess.headers["To"] = "to@test"
assert_equal "sender@test", mess.from
assert_equal "recipient@test", mess.to
assert_equal "This is a test", mess.body
assert_equal "From: from@test", (mess.headers["From"]).to_s
assert_equal "To: to@test", (mess.headers["To"]).to_s
end
end
gurgitate-mail/test/test_gurgitate_delivery.rb 0000644 0000765 0000144 00000023216 10747017435 021775 0 ustar dagbrown users require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
require 'fileutils'
require 'pathname'
require 'irb'
require "test/gurgitate-test"
require "gurgitate-mail"
class TC_Gurgitate_delivery < GurgitateTest
#************************************************************************
# tests
#************************************************************************
def test_basic_delivery
assert_nothing_raised do
@gurgitate.process { nil }
end
assert File.exists?(@spoolfile)
end
def test_detect_mbox
Dir.mkdir(@testdir) rescue nil
File.open(@spoolfile, File::WRONLY | File::CREAT) do |f|
f.print ""
end
assert_nothing_raised do
@gurgitate.process { nil }
end
assert File.exists?(@spoolfile)
end
def test_detect_maildir
maildirmake @spoolfile
assert_nothing_raised do
@gurgitate.process { nil }
end
assert Dir[File.join(@spoolfile,"new","*")].length > 0
assert File.exists?(Dir[File.join(@spoolfile,"new","*")][0])
FileUtils.rmtree @spoolfile
teardown
test_detect_mbox
setup
end
def test_detect_mhdir
mhdirmake @spoolfile
assert_nothing_raised do
@gurgitate.process { nil }
end
assert File.exists?(@spoolfile)
assert File.directory?(@spoolfile)
assert File.exists?(File.join(@spoolfile,"1"))
assert File.exists?(File.join(@spoolfile,".mh_sequences"))
FileUtils.rmtree @spoolfile
teardown
test_detect_mbox
setup
end
def test_save_folders
assert_nothing_raised do
@gurgitate.process do
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).file?
end
def test_save_guess_maildir
maildirmake File.join(@folders,"test")
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert_equal 0, Dir[File.join(@folders, "test", "new", "*")].length
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_nothing_raised do
@gurgitate.process do
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert File.stat(File.join(@folders, "test","new")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_equal 1, Dir[File.join(@folders, "test", "new", "*")].length
end
def test_save_guess_mh
mhdirmake File.join(@folders,"test")
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "*")].length
assert_nothing_raised do
@gurgitate.process do
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "1"))
assert File.stat(File.join(@folders, "test","1")).file?
assert File.exists?(File.join(@folders, "test", ".mh_sequences"))
assert File.stat(File.join(@folders, "test", ".mh_sequences")).file?
assert_equal "unseen: 1\n",
File.read(File.join(@folders, "test", ".mh_sequences"))
end
def test_save_maildir_collision
maildirmake File.join(@folders,"test")
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert_equal 0, Dir[File.join(@folders, "test", "new", "*")].length
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_nothing_raised do
@gurgitate.process do
save "=test"
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert File.stat(File.join(@folders, "test","new")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_equal 2, Dir[File.join(@folders, "test", "new", "*")].length
end
def test_save_mh_collision
mhdirmake File.join(@folders,"test")
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "*")].length
assert_nothing_raised do
@gurgitate.process do
save "=test"
save "=test"
break
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "1"))
assert File.stat(File.join(@folders, "test","1")).file?
assert File.exists?(File.join(@folders, "test", "2"))
assert File.stat(File.join(@folders, "test","2")).file?
assert File.exists?(File.join(@folders, "test", ".mh_sequences"))
assert File.stat(File.join(@folders, "test", ".mh_sequences")).file?
assert_equal "unseen: 1-2\n",
File.read(File.join(@folders, "test", ".mh_sequences"))
end
def test_save_create_maildir
maildirmake @spoolfile
assert_nothing_raised do
@gurgitate.process do
@folderstyle = Gurgitate::Deliver::Maildir
@maildir = @spoolfile
save "=test"
break
end
end
assert File.exists?(File.join(@spoolfile, ".test"))
assert File.stat(File.join(@spoolfile, ".test")).directory?
assert File.exists?(File.join(@spoolfile, ".test", "new"))
assert File.stat(File.join(@spoolfile, ".test","new")).directory?
assert_equal 0, Dir[File.join(@spoolfile, ".test", "cur", "*")].length
assert_equal 1, Dir[File.join(@spoolfile, ".test", "new", "*")].length
end
def test_save_create_mh
maildirmake @spoolfile
assert_nothing_raised do
@gurgitate.process do
@folderstyle = Gurgitate::Deliver::MH
@maildir = @spoolfile
save "=test"
break
end
end
assert File.exists?(File.join(@spoolfile, "test"))
assert File.stat(File.join(@spoolfile, "test")).directory?
assert File.exists?(File.join(@spoolfile, "test", ".mh_sequences"))
assert File.stat(File.join(@spoolfile, "test",".mh_sequences")).file?
assert File.exists?(File.join(@spoolfile, "test", "1"))
assert File.stat(File.join(@spoolfile, "test", "1")).file?
end
def test_save_bad_filename
assert_nothing_raised do
@gurgitate.process do
save "testing"
end
end
assert File.exists?(@spoolfile)
assert !File.exists?("testing")
end
def test_cannot_save
FileUtils.touch @spoolfile
FileUtils.chmod 0, @spoolfile
assert_raises Errno::EACCES do
@gurgitate.process do
nil
end
end
end
def test_mailbox_heuristics_mbox
@gurgitate.instance_eval do
@folderstyle = nil
end
assert_nothing_raised do
@gurgitate.process do
save "=test"
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).file?
end
def test_mailbox_heuristics_maildir
@gurgitate.instance_eval do
@folderstyle = nil
end
assert_nothing_raised do
@gurgitate.process do
save "=test/"
end
end
assert File.exists?(File.join(@folders, "test"))
assert File.stat(File.join(@folders, "test")).directory?
assert File.exists?(File.join(@folders, "test", "new"))
assert File.stat(File.join(@folders, "test","new")).directory?
assert_equal 0, Dir[File.join(@folders, "test", "cur", "*")].length
assert_equal 1, Dir[File.join(@folders, "test", "new", "*")].length
end
def test_message_parsed_correctly
assert_equal("From: me",@gurgitate.header("From"))
assert_equal("To: you", @gurgitate.header("To"))
assert_equal("Subject: test", @gurgitate.header("Subject"))
assert_equal("Hi.\n", @gurgitate.body, "Message body is wrong")
end
def test_message_written_correctly
test_message_parsed_correctly
assert_nothing_raised do
@gurgitate.process
end
mess=nil
assert_nothing_raised do
mess = Gurgitate::Mailmessage.new(File.read(@spoolfile))
end
assert_equal("From: me", mess.header("From"), "From header is wrong")
assert_equal("To: you", mess.header("To"), "To header is wrong")
assert_equal("Hi.\n", mess.body, "Body is wrong")
assert_equal("Subject: test", mess.header("Subject"), "Subject header wrong")
end
end
gurgitate-mail/test/test_headers.rb 0000644 0000765 0000144 00000042371 10747500326 017511 0 ustar dagbrown users #!/opt/bin/ruby -w
#------------------------------------------------------------------------
#
#------------------------------------------------------------------------
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'stringio'
builddir = File.join(File.dirname(__FILE__),"..")
unless $:[0] == builddir
$:.unshift builddir
end
class TC_Headers < Test::Unit::TestCase
def setup
require 'gurgitate-mail'
end
def test_single_header
h=Gurgitate::Headers.new(<<'EOF'
From fromline@example.com Sat Sep 27 12:20:25 PDT 2003
From: fromheader@example.com
EOF
)
assert_equal("fromline@example.com",h.from)
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
end
def test_fromline_simple_username
h=Gurgitate::Headers.new(<<'EOF'
From fromline Sat Sep 27 12:20:25 PDT 2003
From: fromheader@example.com
EOF
)
assert_equal("fromline",h.from)
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
assert_equal nil, h["To"]
end
def test_changing_headers
h = Gurgitate::Headers.new(<<'EOF', "sender@example.com", "recipient@example.com")
From: fromheader@example.com
To: toheader@example.com
Subject: Subject
EOF
assert_equal(1,h["From"].length)
assert_equal("From", h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
h["From"].sub! "fromheader", "changedheader"
assert_equal("changedheader@example.com",h["From"][0].contents)
end
def test_altered_headers
h = Gurgitate::Headers.new(<<'EOF', "sender@example.com", "recipient@example.com")
From: fromheader@example.com
To: toheader@example.com
Subject: Subject
EOF
assert_equal(1,h["From"].length)
assert_equal("From", h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
new_header = h["From"].sub "fromheader", "changedheader"
assert Gurgitate::HeaderBag === new_header
assert_equal("changedheader@example.com",
new_header[0].contents)
assert_equal("fromheader@example.com",h["From"][0].contents)
end
def test_matches
h = Gurgitate::Headers.new(<<'EOF', "sender@example.com", "recipient@example.com")
From: fromheader@example.com
To: toheader@example.com
Subject: Subject
EOF
assert h.matches(["From", "To"], /example.com/)
assert !h.matches(["From", "To"], /example.net/)
assert h.matches("From", /example.com/)
assert !h.matches("From", /example.net/)
end
def test_fromline_no_username
h=Gurgitate::Headers.new(<<'EOF'
From Sat Sep 27 12:20:25 PDT 2003
From: fromheader@example.com
EOF
)
assert_equal("",h.from)
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
end
def test_missing_toline
h=Gurgitate::Headers.new(<<'EOF'
From: fromheader@example.com
EOF
)
assert_equal('fromheader@example.com',h.from)
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
end
def test_sender_and_recipient
h = Gurgitate::Headers.new(<<'EOF', "sender@example.com", "recipient@example.com")
From: fromheader@example.com
To: toheader@example.com
Subject: Subject
EOF
assert_equal('sender@example.com', h.from)
assert_equal('recipient@example.com', h.to)
end
def test_multiple_headers
h=Gurgitate::Headers.new(<<'EOF'
From fromline@example.com Sat Sep 27 12:20:25 PDT 2003
From: fromheader@example.com
To: toheader@example.com
Subject: Subject line
EOF
)
assert_equal(h.from,"fromline@example.com")
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
assert_equal(1,h["To"].length)
assert_equal("To",h["To"][0].name)
assert_equal("toheader@example.com",h["To"][0].contents)
assert_equal(1,h["Subject"].length)
assert_equal("Subject",h["Subject"][0].name)
assert_equal("Subject line",h["Subject"][0].contents)
end
def test_missing_fromline
h=Gurgitate::Headers.new(<<'EOF'
From: fromheader@example.com
To: toheader@example.com
Subject: Subject line
EOF
)
assert_equal('fromheader@example.com',h.from)
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
assert_equal(1,h["To"].length)
assert_equal("To",h["To"][0].name)
assert_equal("toheader@example.com",h["To"][0].contents)
assert_equal(1,h["Subject"].length)
assert_equal("Subject",h["Subject"][0].name)
assert_equal("Subject line",h["Subject"][0].contents)
end
def test_multiline_headers
h=Gurgitate::Headers.new(<<'EOF'
From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
From: fromheader@example.com
To: toheader@example.com,
nexttoheader@example.com
Subject: Subject line
EOF
)
assert_equal(h.from,"fromline@example.com")
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
assert_equal(1,h["To"].length)
assert_equal("To",h["To"][0].name)
assert_equal("toheader@example.com,\n nexttoheader@example.com",h["To"][0].contents)
assert_equal(1,h["Subject"].length)
assert_equal("Subject",h["Subject"][0].name)
assert_equal("Subject line",h["Subject"][0].contents)
end
def test_multiline_headers_with_extra_colons
h=Gurgitate::Headers.new(<<'EOF'
From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
From: fromheader@example.com
To: toheader@example.com,
nexttoheader@example.com (The test: header)
Subject: Subject line
EOF
)
assert_equal(h.from,"fromline@example.com")
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
assert_equal(1,h["To"].length)
assert_equal("To",h["To"][0].name)
assert_equal("toheader@example.com,\n nexttoheader@example.com (The test: header)",h["To"][0].contents)
assert_equal(1,h["Subject"].length)
assert_equal("Subject",h["Subject"][0].name)
assert_equal("Subject line",h["Subject"][0].contents)
end
def test_multiline_headers_with_various_levels_of_indentation
h=Gurgitate::Headers.new(<<'EOF'
From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
From: fromheader@example.com
To: toheader@example.com,
nexttoheader@example.com,
thirdtoheader@example.com,
fourthtoheader@example.com
Subject: Subject line
EOF
)
assert_equal(h.from,"fromline@example.com")
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("fromheader@example.com",h["From"][0].contents)
assert_equal(1,h["To"].length)
assert_equal("To",h["To"][0].name)
assert_equal("toheader@example.com,\n nexttoheader@example.com,\n thirdtoheader@example.com,\n fourthtoheader@example.com",h["To"][0].contents)
assert_equal(1,h["Subject"].length)
assert_equal("Subject",h["Subject"][0].name)
assert_equal("Subject line",h["Subject"][0].contents)
end
def test_a_header_that_actually_crashed_gurgitate
h=Gurgitate::Headers.new(<<'EOF'
Return-path:
Received: from pd8mr3no.prod.shaw.ca
(pd8mr3no-qfe2.prod.shaw.ca [10.0.144.160]) by l-daemon
(iPlanet Messaging Server 5.2 HotFix 1.16 (built May 14 2003))
with ESMTP id <0HO6002FDGPREL@l-daemon> for dagbrown@shaw.ca; Tue,
11 Nov 2003 00:56:15 -0700 (MST)
Received: from pd7mi4no.prod.shaw.ca ([10.0.149.117])
by l-daemon (iPlanet Messaging Server 5.2 HotFix 1.18 (built Jul 28 2003))
with ESMTP id <0HO60055LGPR40@l-daemon> for dagbrown@shaw.ca
(ORCPT dagbrown@shaw.ca); Tue, 11 Nov 2003 00:56:15 -0700 (MST)
Received: from venom.easydns.com (smtp.easyDNS.com [216.220.40.247])
by l-daemon (iPlanet Messaging Server 5.2 HotFix 1.18 (built Jul 28 2003))
with ESMTP id <0HO60079HGPR79@l-daemon> for dagbrown@shaw.ca; Tue,
11 Nov 2003 00:56:15 -0700 (MST)
Received: from ohno.mrbill.net (ohno.mrbill.net [207.200.6.75])
by venom.easydns.com (Postfix) with ESMTP id D6493722BB for
; Tue, 11 Nov 2003 02:53:50 -0500 (EST)
Received: by ohno.mrbill.net (Postfix) id ED0AD53380; Tue,
11 Nov 2003 01:56:13 -0600 (CST)
Received: from mail.neurotica.com (neurotica.com [207.100.203.161])
by ohno.mrbill.net (Postfix) with ESMTP id 5CD465337F for
; Tue, 11 Nov 2003 01:56:13 -0600 (CST)
Received: from mail.neurotica.com (localhost [127.0.0.1])
by mail.neurotica.com (Postfix) with ESMTP id CDAA2364C; Tue,
11 Nov 2003 02:56:03 -0500 (EST)
Received: from smtpzilla5.xs4all.nl (smtpzilla5.xs4all.nl [194.109.127.141])
by mail.neurotica.com (Postfix) with ESMTP id B6A22361E for
; Tue, 11 Nov 2003 02:56:00 -0500 (EST)
Received: from xs1.xs4all.nl (xs1.xs4all.nl [194.109.21.2])
by smtpzilla5.xs4all.nl (8.12.9/8.12.9) with ESMTP id hAB7u5ZZ042116 for
; Tue, 11 Nov 2003 08:56:05 +0100 (CET)
Received: from xs1.xs4all.nl (wstan@localhost.xs4all.nl [127.0.0.1])
by xs1.xs4all.nl (8.12.10/8.12.9) with ESMTP id hAB7u5xE048677 for
; Tue,
11 Nov 2003 08:56:05 +0100 (CET envelope-from wstan@xs4all.nl)
Received: (from wstan@localhost) by xs1.xs4all.nl (8.12.10/8.12.9/Submit)
id hAB7u4sZ048676 for nifty@neurotica.com; Tue,
11 Nov 2003 08:56:04 +0100 (CET envelope-from wstan)
Date: Tue, 11 Nov 2003 08:56:04 +0100
From: William Staniewicz
Subject: Re: [nifty] Ping...
In-reply-to: <9636B78C-140B-11D8-9EE6-003065D0C184@nimitzbrood.com>
Sender: nifty-bounces@neurotica.com
To: Nifty
Cc:
Errors-to: nifty-bounces@neurotica.com
Reply-to: Nifty
Message-id: <20031111075604.GE79497@xs4all.nl>
MIME-version: 1.0
Content-type: text/plain; charset=us-ascii
Content-disposition: inline
Precedence: list
X-BeenThere: nifty@mail.neurotica.com
Delivered-to: dagbrown@mrbill.net
Delivered-to: nifty@neurotica.com
User-Agent: Mutt/1.4.1i
X-Original-To: nifty@neurotica.com
References: <9636B78C-140B-11D8-9EE6-003065D0C184@nimitzbrood.com>
X-Mailman-Version: 2.1.2
List-Post:
List-Subscribe: ,
List-Unsubscribe: ,
List-Help:
List-Id: Nifty
Original-recipient: rfc822;dagbrown@shaw.ca
EOF
)
assert_equal(h.from,"nifty-bounces@neurotica.com")
assert_equal(1,h["From"].length)
assert_equal("From",h["From"][0].name)
assert_equal("William Staniewicz ",h["From"][0].contents)
assert_equal(1,h["To"].length)
assert_equal("To",h["To"][0].name)
assert_equal('Nifty ',h["To"][0].contents)
assert_equal(1,h["Subject"].length)
assert_equal("Subject",h["Subject"][0].name)
assert_equal("Re: [nifty] Ping...",h["Subject"][0].contents)
end
def test_another_crashy_set_of_headers
h=Gurgitate::Headers.new(<<'EOF'
From HEYITBLEWUP Fri Nov 21 14:41:08 PST 2003
Received: from unknown (harley.radius [192.168.0.123]) by yoda.radius with SMTP (Microsoft Exchange Internet Mail Service Version 5.5.2653.13)
id LYN7YZKG; Wed, 9 Jul 2003 14:36:40 -0700
Subject: IAP password
EOF
)
assert_equal(h.from,"HEYITBLEWUP")
assert_equal(nil,h["From"])
assert_equal("IAP password",h["Subject"][0].contents)
end
def test_fromline_no_hostname # illegal from line
m=<<'EOF'
From HEYITBLEWUP Sat Mar 27 16:02:12 PST 2004
Received: from ohno.mrbill.net (ohno.mrbill.net [207.200.6.75])
by lart.ca (Postfix) with ESMTP id A485F104CA9
for ; Sat, 27 Mar 2004 15:58:06 -0800 (PST)
Received: by ohno.mrbill.net (Postfix)
id 0D3423A289; Sat, 27 Mar 2004 17:58:42 -0600 (CST)
Delivered-To: dagbrown@mrbill.net
Received: from 66-168-59-126.jvl.wi.charter.com (66-168-59-126.jvl.wi.charter.com [66.168.59.126])
by ohno.mrbill.net (Postfix) with SMTP id 948BD3A288
for ; Sat, 27 Mar 2004 17:58:41 -0600 (CST)
X-Message-Info: HOCBSQX
Message-Id: <20040327235841.948BD3A288@ohno.mrbill.net>
Date: Sat, 27 Mar 2004 17:58:41 -0600 (CST)
From: ""@
To: undisclosed-recipients: ;
EOF
h=Gurgitate::Headers.new(m)
assert_equal(%{""@},h["From"][0].contents)
assert_equal(1,h["From"].length)
end
def test_editing_header
m = <<'EOF'
From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
From: fromline@example.com
To: toline@example.com
Subject: Subject line
EOF
h=Gurgitate::Headers.new(m)
h["From"]="anotherfromline@example.com"
assert_equal("anotherfromline@example.com",h["From"][0].contents,
"From line correctly changed")
assert_match(/^From: anotherfromline@example.com$/,h.to_s,
"From line correctly turns up in finished product")
end
def test_editing_from
m = <<'EOF'
From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
From: fromline@example.com
To: toline@example.com
Subject: Subject line
EOF
h=Gurgitate::Headers.new(m)
t=Time.new.to_s
h.from="anotherfromline@example.com"
assert_equal("anotherfromline@example.com",h.from,
"Envelope from correctly changed")
assert_match(/^From anotherfromline@example.com #{Regexp.escape(t)}/,
h.to_mbox, "Envelope from changed in finished product")
end
def test_match_multiple_headers
m = <<'EOF'
From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
From: fromline@example.com
To: toline@example.com
Subject: Subject line
EOF
h=Gurgitate::Headers.new(m)
assert_equal(true,h["From","To"] =~ /fromline@example.com/,
"headers contains fromline")
assert_equal(true,h["From","To"] =~ /toline@example.com/,
"headers contains toline")
assert_equal(false,h["From","To"] =~ /nonexistent@example.com/,
"headers do not contain nonexistent value")
assert(!(h["Rabbit"] =~ /nonexistent/),
"Asking for a nonexistent header")
end
def test_broken_spam
m=<<'EOF'
Return-Path: kirstenparsonsry@yahoo.com
Delivery-Date: Fri May 21 19:42:02 PDT
Return-Path: kirstenparsonsry@yahoo.com
Delivery-Date: Fri May 21 17:39:51 2004
Return-Path:
X-Original-To: dagbrown@lart.ca
Delivered-To: dagbrown@lart.ca
Received: from anest.co.jp (c-24-1-221-189.client.comcast.net [24.1.221.189])
by lart.ca (Postfix) with ESMTP id 05B7F5704
for ; Fri, 21 May 2004 17:39:51 -0700 (PDT)
Message-ID:
From: "Kirsten Parsons"
To: dagbrown@lart.ca
Subject: Congrats!
Date: Fri, 21 May 2004 20:56:27 +0000
MIME-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: base64
EOF
h=Gurgitate::Headers.new(m)
assert_equal(Gurgitate::Header.new("To","dagbrown@lart.ca").contents,
h["To"][0].contents,"To header is as expected")
assert_equal(false,h["To","Cc"] =~ /\blunar@lunar-linux.org\b/i,
"There should be no Lunar Linux mailing list here")
assert_equal(false,h["To"] =~ /\blunar@lunar-linux.org\b/i,
"There should be no Lunar Linux mailing list in To line")
assert(!(h["Cc"] =~ /\blunar@lunar-linux.org\b/i),
"There should be no Lunar Linux mailing list in Cc line")
end
end
class TC_Meddling_With_Headers < Test::Unit::TestCase
def setup
@message = <<'EOF'
From: fromline@example.com
To: toline@example.com
Subject: Subject Line
EOF
@sender = "sender@example.com"
@recipient = "recipient@example.com"
@headers = Gurgitate::Headers.new @message, @sender, @recipient
end
def test_match
assert @headers.match("From", /example.com/)
end
def test_nomatch
assert !@headers.match("From", /lart.ca/)
end
def test_match_regex
result = nil
assert_nothing_raised do
result = @headers.match "From", /example.com/
end
assert result
end
def test_match_string
result = nil
assert_nothing_raised do
assert result = @headers.match("From", "example.com")
end
assert result
result = nil
assert_nothing_raised do
result = @headers.match("From", "e.ample.com")
end
assert !result
end
end