Repository URL to install this package:
|
Version:
1.3.2 ▾
|
#!/usr/bin/env ruby
#
# hermesmail -- Mail filtering and delivery
#
begin
require "appl"
rescue LoadError
raise "This requires the Gem 'appl'."
end
require "hermes/version"
require "hermes/transports"
require "hermes/cli/pop"
module Hermes
class Processed < Mail
attr_accessor :debug
class Done < Exception ; end
# Do nothing, just finish.
def done
raise Done
end
alias delete done
# Save in a local mailbox
def deposit mailbox = nil
save mailbox
done
end
# Forward by SMTP
def forward_smtp to
send nil, to
done
end
# Forward by sendmail
def forward_sendmail to
sendmail to
done
end
alias forward forward_smtp
self.logfile = "hermesmail.log"
self.loglevel = :ERR
@failed_process = "=failed-process"
class <<self
attr_accessor :failed_process
def process input, debug = false
i = parse input
i.debug = debug
i.execute
rescue
raise if debug
open_failed { |f|
log_exception "Error while parsing mail", f.path
f.write input
}
end
def log_exception msg, *args
log :ERR, "#{msg}: #$! (#{$!.class})", *args
$!.backtrace.each { |b| log :INF, " #{b}" }
end
private
def open_failed
i = 0
d = expand_sysdir
w = Time.now.strftime "%Y%m%d%H%M%S"
begin
p = File.join d, "failed-#{w}-%05d" % i
File.open p, File::CREAT|File::EXCL|File::WRONLY do |f|
yield f
end
rescue Errno::ENOENT
Dir.mkdir! d and retry
rescue Errno::EEXIST
i +=1
retry
end
end
end
def execute
process
save
rescue Done
rescue
raise if @debug
log_exception "Error while processing mail"
b = cls.box cls.failed_process
save b
end
def log_exception msg, *args
cls.log_exception msg, *args
end
end
class Fetch
class <<self
private :new
def create *args, &block
i = new
i.instance_eval *args, &block
def i.each
@list.each { |a|
c = a[ :type].new *a[ :args]
a[ :logins].each { |l|
c.login *l do yield c end
}
}
end
i
end
end
def initialize
@list = []
end
def pop *args ; access Cli::Pop, *args do yield end ; end
def pops *args ; access Cli::Pops, *args do yield end ; end
def login *args
@access[ :logins].push args
nil
end
private
def access type, *args
@access and raise "Access methods must not be nested."
@access = { type: type, args: args, logins: [] }
yield
@list.push @access
nil
ensure
@access = nil
end
end
class MailApp < Application
NAME = "hermesmail"
VERSION = Hermes::VERSION
SUMMARY = "A mail delivery agent written in Ruby"
COPYRIGHT = Hermes::COPYRIGHT
LICENSE = Hermes::LICENSE
AUTHORS = Hermes::AUTHORS
DESCRIPTION = <<-EOT
This mail delivery agent (MDA) reads a configuration file
that is plain Ruby code. See the examples section for how
to write one.
EOT
attr_accessor :rulesfile, :mbox, :fetchfile
attr_bang :debug, :fetch, :keep
def quiet! ; @quiet += 1 ; end
def initialize *args
@quiet = 0
super
$*.replace @args
end
RULESFILE = "~/.hermesmail-rules"
FETCHFILE = "~/.hermesmail-fetch"
define_option "r", :rulesfile=, "NAME", RULESFILE, "filtering rules"
alias_option "r", "rulesfile"
define_option "M", :mbox=, "MBOX",
"process all in MBOX instead of one from stdin"
alias_option "M", "mbox"
define_option "f", :fetch!, "fetch from a POP server"
alias_option "f", "fetch"
define_option "F", :fetchfile=, "FILE", FETCHFILE,
"a PGP-encrypted file containing fetch methods"
alias_option "F", "fetchfile"
alias_option "F", "fetch-file"
define_option "k", :keep!, "don't delete the mails on the server"
alias_option "k", "keep"
define_option "q", :quiet!,
"less output (once = no progress, twice = nothing)"
alias_option "q", "quiet"
define_option "g", :debug!, "full Ruby error messages"
alias_option "g", "debug"
define_option "h", :help, "show options"
alias_option "h", "help"
define_option "V", :version, "show version"
alias_option "V", "version"
def run
Processed.class_eval read_rules
if @mbox and @fetch then
raise "Specify either mbox or fetch but not both."
end
if @mbox then
b = Box.find @mbox
b.each { |m| Processed.process m }
elsif @fetch then
read_fetches.each { |s|
c = s.count
puts "#{c} Mails in #{s.name}." if @quiet < 2
i = 0
s.each { |m|
print "\r#{i}/#{c} " if @quiet < 1
i += 1
Processed.process m
raise Cli::Pop::Keep if @keep
}
puts "\rDone. " if @quiet < 1
}
else
msg = $<.read
msg.force_encoding Encoding::ASCII_8BIT
Processed.process msg, @debug
end
end
private
def read_rules
r = File.expand_path @rulesfile
File.read r
end
def read_fetches
p = File.expand_path @fetchfile
Fetch.create `gpg -d #{p}`
end
end
MailApp.run
end