Class: Gurgitate::Gurgitate

Inherits:
Mailmessage show all
Includes:
Deliver
Defined in:
lib/gurgitate-mail.rb

Overview

This is the actual gurgitator; it reads a message and then it can do other stuff with it, like saving it to a mailbox or forwarding it somewhere else.

To set configuration parameters for gurgitate-mail, use a keyword- based system. It’s almost like an attribute, only if you give the accessor a parameter, it will set the configuration parameter to the parameter’s value. For instance:

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, self.name = value still works.)

The attributes you can define are:

homedir

Your home directory. This defaults to what your actual home directory is.

maildir

The directory you store your mail in. This defaults to the “Mail” directory in your home dir.

logfile

The path to your gurgitate-mail log file. If you set this to nil, gurgitate-mail won’t log anything. The default value is “.gurgitate.log” in your home directory.

The following parameters are more likely to be interesting to the system administrator than the everyday user.

sendmail

The full path of your “sendmail” program, or at least a program that provides functionality equivalent to sendmail.

spoolfile

The default location to store mail messages, for the messages that have been unaffected by your gurgitate rules. If an exception is raised by your rules, the message will be delivered to the spoolfile.

spooldir

The location where users’ system mail boxes live.

folderstyle

The style of mailbox to create (and to expect, although gurgitate-mail automatically detects the type of existing mailboxes). See the separate documentation for folderstyle for more details.

Constant Summary

Constants inherited from Mailmessage

Mailmessage::Fromregex

Instance Attribute Summary

Attributes inherited from Mailmessage

#recipient, #sender

Attributes inherited from Message

#body, #headers

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Deliver

#save

Methods inherited from Mailmessage

create, #from, #to, #to_mbox

Methods inherited from Message

create, #header, #to_s

Constructor Details

#initialize(input = nil, recipient = nil, sender = nil, spooldir = "/var/spool/mail", &block) ⇒ Gurgitate

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.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/gurgitate-mail.rb', line 204

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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth) ⇒ Object

This is kind of neat. You can get a header by calling its name as a method. For example, 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.



268
269
270
271
272
273
274
275
# File 'lib/gurgitate-mail.rb', line 268

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

Class Method Details

.attr_configparam(*syms) ⇒ Object

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.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/gurgitate-mail.rb', line 89

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

Instance Method Details

#add_rules(filename, options = {}) ⇒ Object

:nodoc:



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/gurgitate-mail.rb', line 225

def add_rules(filename, options = {}) #:nodoc:
    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

#deleteObject

Deletes (discards) the current message.



256
257
258
# File 'lib/gurgitate-mail.rb', line 256

def delete
    # Well, nothing here, really.
end

#filter(program, &block) ⇒ Object

Pipes the message through program, and returns another Gurgitate object containing the output of the filter

Use it like this:

filter "bogofilter -p" do
  if x_bogosity =~ /Spam/ then
    log "Found spam"
    delete
    return
  end
end


322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/gurgitate-mail.rb', line 322

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

#folderstyle(*style) ⇒ Object

What kind of mailboxes you prefer. Treat this like a configuration parameter. If no argument is given, then return the current default type.

Depending on what you set this to, some other configuration parameters change. You can set this to the following things:

Maildir

Create Maildir mailboxes.

This sets spooldir to your home directory, spoolfile to $HOME/Maildir and creates mail folders underneath that.

MH

Create MH mail boxes.

This reads your .mh_profile file to find out where you’ve told MH to find its mail folders, and uses that value. If it can’t find that in your .mh_profile, it will assume you want mailboxes in $HOME/Mail. It sets spoolfile to “inbox” in your mail directory.

Mbox

Create mbox mailboxes.

This sets spooldir to /var/spool/mail and spoolfile to a file with your username in /var/spool/mail.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/gurgitate-mail.rb', line 163

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

#forward(address) ⇒ Object

Forwards the message to address.

address

A valid email address to forward the message to.



281
282
283
284
285
286
# File 'lib/gurgitate-mail.rb', line 281

def forward(address)
    self.log "Forwarding to "+address
    IO.popen(@sendmail+" "+address,"w") do |f|
        f.print(self.to_s)
    end
end

#log(message) ⇒ Object

Writes message to the log file.



289
290
291
292
293
294
295
296
297
# File 'lib/gurgitate-mail.rb', line 289

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

#pipe(program) ⇒ Object

Pipes the message through program. If program fails, puts the message into spoolfile



301
302
303
304
305
306
307
# File 'lib/gurgitate-mail.rb', line 301

def pipe(program)
    self.log "Piping through "+program
    IO.popen(program,"w") do |f|
        f.print(self.to_s)
    end
    return $?>>8
end

#process(&block) ⇒ Object

:nodoc:



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/gurgitate-mail.rb', line 352

def process(&block) #:nodoc:
    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