gurgitate-mail 1.10.9 → 1.10.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 578b870727d42aad907a83379eff2df9dab9c704
4
+ data.tar.gz: f76eaacb28f9cbc0582241dacc17f82db06a5013
5
+ SHA512:
6
+ metadata.gz: b636bf531b2c0a5d217d9a673d9b52e3cd7fa01ae8306380f7a8d3e0cb97778dfcc0eec7e0b1774fb247c1b542da34899b5612bdb2e176f0ea7887ce08326434
7
+ data.tar.gz: a99dacff953b461fce793845509976505e9e2840d272dd18a4c96e1427810c4d94a794b0d7d949250dedd6c9eeed273872d9a380e3db58a5034834ff2d1cf0cb
@@ -0,0 +1,386 @@
1
+ #!/opt/bin/ruby
2
+ # -*- encoding : utf-8 -*-
3
+ #------------------------------------------------------------------------
4
+ # Mail filter package
5
+ #------------------------------------------------------------------------
6
+
7
+ require 'etc'
8
+
9
+ require 'gurgitate/mailmessage'
10
+ require 'gurgitate/deliver'
11
+
12
+ module Gurgitate
13
+ # This is the actual gurgitator; it reads a message and then it can
14
+ # do other stuff with it, like saving it to a mailbox or forwarding
15
+ # it somewhere else.
16
+ #
17
+ # To set configuration parameters for gurgitate-mail, use a keyword-
18
+ # based system. It's almost like an attribute, only if you give the
19
+ # accessor a parameter, it will set the configuration parameter to
20
+ # the parameter's value. For instance:
21
+ #
22
+ # maildir "#{homedir}/Mail"
23
+ # sendmail "/usr/sbin/sendmail"
24
+ # spoolfile "Maildir"
25
+ # spooldir homedir
26
+ #
27
+ # (This is because of an oddity in Ruby where, even if an
28
+ # accessor exists in the current object, if you say:
29
+ # name = value
30
+ # it'll always create a local variable. Not quite what you
31
+ # want when you're trying to set a config parameter. You have
32
+ # to say <code>self.name = value</code>, which [I think] is ugly.
33
+ #
34
+ # In the interests of promoting harmony, of course,
35
+ # <code>self.name = value</code> still works.)
36
+ #
37
+ # The attributes you can define are:
38
+ #
39
+ # homedir :: Your home directory. This defaults to what your
40
+ # actual home directory is.
41
+ #
42
+ # maildir :: The directory you store your mail in. This defaults
43
+ # to the "Mail" directory in your home dir.
44
+ #
45
+ # logfile :: The path to your gurgitate-mail log file. If you
46
+ # set this to +nil+, gurgitate-mail won't log anything.
47
+ # The default value is ".gurgitate.log" in your home
48
+ # directory.
49
+ #
50
+ # The following parameters are more likely to be interesting to the
51
+ # system administrator than the everyday user.
52
+ #
53
+ # sendmail :: The full path of your "sendmail" program, or at least
54
+ # a program that provides functionality equivalent to
55
+ # sendmail.
56
+ #
57
+ # spoolfile :: The default location to store mail messages, for the
58
+ # messages that have been unaffected by your gurgitate
59
+ # rules. If an exception is raised by your rules, the
60
+ # message will be delivered to the spoolfile.
61
+ #
62
+ # spooldir :: The location where users' system mail boxes live.
63
+ #
64
+ # folderstyle :: The style of mailbox to create (and to expect,
65
+ # although gurgitate-mail automatically detects the
66
+ # type of existing mailboxes). See the separate
67
+ # documentation for folderstyle for more details.
68
+ class Gurgitate < Mailmessage
69
+ include Deliver
70
+
71
+ # Instead of the usual attributes, I went with a
72
+ # reader-is-writer type thing (as seen quite often in Perl and
73
+ # C++ code) so that in your .gurgitate-rules, you can say
74
+ #
75
+ # maildir "#{homedir}/Mail"
76
+ # sendmail "/usr/sbin/sendmail"
77
+ # spoolfile "Maildir"
78
+ # spooldir homedir
79
+ #
80
+ # This is because of an oddity in Ruby where, even if an
81
+ # accessor exists in the current object, if you say:
82
+ # name = value
83
+ # it'll always create a local variable. Not quite what you
84
+ # want when you're trying to set a config parameter. You have
85
+ # to say "self.name = value", which (I think) is ugly.
86
+ #
87
+ # In the interests of promoting harmony, of course, the previous
88
+ # syntax will continue to work.
89
+ def self.attr_configparam(*syms)
90
+ syms.each do |sym|
91
+ class_eval %{
92
+ def #{sym} (*vals)
93
+ if vals.length == 1
94
+ @#{sym} = vals[0]
95
+ elsif vals.length == 0
96
+ @#{sym}
97
+ else
98
+ raise ArgumentError,
99
+ "wrong number of arguments " +
100
+ "(\#{vals.length} for 0 or 1)"
101
+ end
102
+ end
103
+
104
+ # Don't break it for the nice people who use
105
+ # old-style accessors though. Breaking people's
106
+ # .gurgitate-rules is a bad idea.
107
+ attr_writer :#{sym}
108
+ }
109
+ end
110
+ end
111
+
112
+ # The directory you want to put mail folders into
113
+ attr_configparam :maildir
114
+
115
+ # The path to your log file
116
+ attr_configparam :logfile
117
+
118
+ # The full path of your "sendmail" program
119
+ attr_configparam :sendmail
120
+
121
+ # Your home directory
122
+ attr_configparam :homedir
123
+
124
+ # Your default mail spool
125
+ attr_configparam :spoolfile
126
+
127
+ # The directory where user mail spools live
128
+ attr_configparam :spooldir
129
+
130
+ # What kind of mailboxes you prefer
131
+ # attr_configparam :folderstyle
132
+
133
+ # What kind of mailboxes you prefer. Treat this like a
134
+ # configuration parameter. If no argument is given, then
135
+ # return the current default type.
136
+ #
137
+ # Depending on what you set this to, some other configuration
138
+ # parameters change. You can set this to the following things:
139
+ #
140
+ # <code>Maildir</code> :: Create Maildir mailboxes.
141
+ #
142
+ # This sets +spooldir+ to your home
143
+ # directory, +spoolfile+ to
144
+ # $HOME/Maildir and creates
145
+ # mail folders underneath that.
146
+ #
147
+ # <code>MH</code> :: Create MH mail boxes.
148
+ #
149
+ # This reads your <code>.mh_profile</code>
150
+ # file to find out where you've told MH to
151
+ # find its mail folders, and uses that value.
152
+ # If it can't find that in your .mh_profile,
153
+ # it will assume you want mailboxes in
154
+ # $HOME/Mail. It sets +spoolfile+ to
155
+ # "inbox" in your mail directory.
156
+ #
157
+ # <code>Mbox</code> :: Create +mbox+ mailboxes.
158
+ #
159
+ # This sets +spooldir+ to
160
+ # <code>/var/spool/mail</code> and
161
+ # +spoolfile+ to a file with your username
162
+ # in <code>/var/spool/mail</code>.
163
+ def folderstyle(*style)
164
+ if style.length == 0 then
165
+ @folderstyle
166
+ elsif style.length == 1 then
167
+ if style[0] == Maildir then
168
+ spooldir homedir
169
+ spoolfile File.join(spooldir,"Maildir")
170
+ maildir spoolfile
171
+ elsif style[0] == MH then
172
+ mh_profile_path = File.join(ENV["HOME"],".mh_profile")
173
+ if File.exists?(mh_profile_path) then
174
+ mh_profile = YAML.load(File.read(mh_profile_path))
175
+ maildir mh_profile["Path"]
176
+ else
177
+ maildir File.join(ENV["HOME"],"Mail")
178
+ end
179
+ spoolfile File.join(maildir,"inbox")
180
+ else
181
+ spooldir "/var/spool/mail"
182
+ spoolfile File.join(spooldir, @passwd.name)
183
+ end
184
+
185
+ @folderstyle = style[0]
186
+ else
187
+ raise ArgumentError, "wrong number of arguments "+
188
+ "(#{style.length} for 0 or 1)"
189
+ end
190
+ @folderstyle
191
+ end
192
+
193
+ # Set config params to defaults, read in mail message from
194
+ # +input+
195
+ # input::
196
+ # Either the text of the email message in RFC-822 format,
197
+ # or a filehandle where the email message can be read from
198
+ # recipient::
199
+ # The contents of the envelope recipient parameter
200
+ # sender::
201
+ # The envelope sender parameter
202
+ # spooldir::
203
+ # The location of the mail spools directory.
204
+ def initialize(input=nil,
205
+ recipient=nil,
206
+ sender=nil,
207
+ spooldir="/var/spool/mail",
208
+ &block)
209
+ @passwd = Etc.getpwuid
210
+ @homedir = @passwd.dir;
211
+ @maildir = File.join(@passwd.dir,"Mail")
212
+ @logfile = File.join(@passwd.dir,".gurgitate.log")
213
+ @sendmail = "/usr/lib/sendmail"
214
+ @spooldir = spooldir
215
+ @spoolfile = File.join(@spooldir,@passwd.name )
216
+ @folderstyle = MBox
217
+ @rules = []
218
+
219
+ input_text = ""
220
+ input.each_line do |l| input_text << l end
221
+ super(input_text, recipient, sender)
222
+ instance_eval(&block) if block_given?
223
+ end
224
+
225
+ def add_rules(filename, options = {}) #:nodoc:
226
+ if not Hash === options
227
+ raise ArgumentError.new("Expected hash of options")
228
+ end
229
+ if filename == :default
230
+ filename=homedir+"/.gurgitate-rules"
231
+ end
232
+ if not FileTest.exist?(filename)
233
+ filename = filename + '.rb'
234
+ end
235
+ if not FileTest.exist?(filename)
236
+ if options.has_key?(:user)
237
+ log("#{filename} does not exist.")
238
+ end
239
+ return false
240
+ end
241
+ if FileTest.file?(filename) and
242
+ ( ( not options.has_key? :system and
243
+ FileTest.owned?(filename) ) or
244
+ ( options.has_key? :system and
245
+ options[:system] == true and
246
+ File.stat(filename).uid == 0 ) ) and
247
+ FileTest.readable?(filename)
248
+ @rules << filename
249
+ else
250
+ log("#{filename} has bad permissions or ownership, not using rules")
251
+ return false
252
+ end
253
+ end
254
+
255
+ # Deletes (discards) the current message.
256
+ def delete
257
+ # Well, nothing here, really.
258
+ end
259
+
260
+ # This is kind of neat. You can get a header by calling its
261
+ # name as a method. For example, if you want the header
262
+ # "X-Face", then you call x_face and that gets it for you. It
263
+ # raises NameError if that header isn't found.
264
+ #
265
+ # meth::
266
+ # The method that the caller tried to call which isn't
267
+ # handled any other way.
268
+ def method_missing(meth)
269
+ headername=meth.to_s.split(/_/).map {|x| x.capitalize}.join("-")
270
+ if headers[headername] then
271
+ return headers[headername]
272
+ else
273
+ raise NameError,"undefined local variable or method, or header not found `#{meth}' for #{self}:#{self.class}"
274
+ end
275
+ end
276
+
277
+ # Forwards the message to +address+.
278
+ #
279
+ # address::
280
+ # A valid email address to forward the message to.
281
+ def forward(address)
282
+ self.log "Forwarding to "+address
283
+ IO.popen(@sendmail+" "+address,"w") do |f|
284
+ f.print(self.to_s)
285
+ end
286
+ end
287
+
288
+ # Writes +message+ to the log file.
289
+ def log(message)
290
+ if @logfile then
291
+ File.open(@logfile,"a") do |f|
292
+ f.flock(File::LOCK_EX)
293
+ f.print(Time.new.to_s+" "+message+"\n")
294
+ f.flock(File::LOCK_UN)
295
+ end
296
+ end
297
+ end
298
+
299
+ # Pipes the message through +program+. If +program+
300
+ # fails, puts the message into +spoolfile+
301
+ def pipe(program)
302
+ self.log "Piping through "+program
303
+ IO.popen(program,"w") do |f|
304
+ f.print(self.to_s)
305
+ end
306
+ return $?>>8
307
+ end
308
+
309
+ # Pipes the message through +program+, and returns another
310
+ # +Gurgitate+ object containing the output of the filter
311
+ #
312
+ # Use it like this:
313
+ #
314
+ # filter "bogofilter -p" do
315
+ # if x_bogosity =~ /Spam/ then
316
+ # log "Found spam"
317
+ # delete
318
+ # return
319
+ # end
320
+ # end
321
+ #
322
+ def filter(program,&block)
323
+ self.log "Filtering with "+program
324
+ IO.popen("-","w+") do |filter|
325
+ unless filter then
326
+ begin
327
+ exec(program)
328
+ rescue
329
+ exit! # should not get here anyway
330
+ end
331
+ else
332
+ if fork
333
+ filter.close_write
334
+ g=Gurgitate.new(filter)
335
+ g.instance_eval(&block) if block_given?
336
+ return g
337
+ else
338
+ begin
339
+ filter.close_read
340
+ filter.print to_s
341
+ filter.close
342
+ rescue
343
+ nil
344
+ ensure
345
+ exit!
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ def process(&block) #:nodoc:
353
+ begin
354
+ if @rules.size > 0 or block
355
+ @rules.each do |configfilespec|
356
+ begin
357
+ eval File.new(configfilespec).read, nil,
358
+ configfilespec
359
+ rescue ScriptError
360
+ log "Couldn't load #{configfilespec}: "+$!
361
+ save(spoolfile)
362
+ rescue Exception
363
+ log "Error while executing #{configfilespec}: #{$!}"
364
+ $@.each { |tr| log "Backtrace: #{tr}" }
365
+ folderstyle MBox
366
+ save(spoolfile)
367
+ end
368
+ end
369
+ if block
370
+ instance_eval(&block)
371
+ end
372
+ log "Mail not covered by rules, saving to default spool"
373
+ save(spoolfile)
374
+ else
375
+ save(spoolfile)
376
+ end
377
+ rescue Exception
378
+ log "Error while executing rules: #{$!}"
379
+ $@.each { |tr| log "Backtrace: #{tr}" }
380
+ log "Attempting to save to spoolfile after error"
381
+ folderstyle MBox
382
+ save(spoolfile)
383
+ end
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,87 @@
1
+ #!/opt/bin/ruby -w
2
+ # -*- encoding : utf-8 -*-
3
+
4
+ #------------------------------------------------------------------------
5
+ # Code to handle saving a message to a mailbox (and a framework for detecting
6
+ # what kind of mailbox it is)
7
+ #------------------------------------------------------------------------
8
+
9
+ require "gurgitate/deliver/mbox"
10
+ require "gurgitate/deliver/maildir"
11
+ require "gurgitate/deliver/mh"
12
+
13
+ module Gurgitate
14
+ module Deliver
15
+
16
+ class MailboxFound < Exception
17
+ # more of a "flag" than an exception really
18
+ end
19
+
20
+ # Saves a message to +mailbox+, after detecting what the mailbox's
21
+ # format is.
22
+ # mailbox::
23
+ # A string containing the path of the mailbox to save
24
+ # the message to. If it is of the form "=mailbox", it
25
+ # saves the message to +Maildir+/+mailbox+. Otherwise,
26
+ # it simply saves the message to the file +mailbox+.
27
+ def save(mailbox)
28
+
29
+ if mailbox[0,1]=='=' and @maildir != nil then
30
+ if @folderstyle == Maildir and mailbox !~ /^=\./ then
31
+ mailbox["="]=@maildir+"/."
32
+ else
33
+ mailbox["="]=@maildir+"/"
34
+ end
35
+ end
36
+
37
+ if mailbox[0,1] != '/' then
38
+ log("Cannot save to relative filenames! Saving to spool file");
39
+ mailbox=spoolfile
40
+ end
41
+
42
+ begin
43
+ [MBox,Maildir,MH].each do |mod|
44
+ if mod::check_mailbox(mailbox) then
45
+ extend mod
46
+ raise MailboxFound
47
+ end
48
+ end
49
+
50
+ # Huh, nothing could find anything. Oh well,
51
+ # let's default to whatever's in @folderstyle. (I
52
+ # guess we'll be making a new mailbox, eh?)
53
+
54
+ if Module === @folderstyle then
55
+ #
56
+ # Careful we don't get the wrong instance variable
57
+ folderstyle=@folderstyle
58
+
59
+ extend folderstyle
60
+ else
61
+ # No hints from the user either. Let's guess!
62
+ # I'll use the same heuristic that Postfix uses--if the
63
+ # mailbox name ends with a /, then make it a Maildir,
64
+ # otherwise make it a mail file
65
+ if mailbox =~ /\/$/ then
66
+ extend Maildir
67
+ else
68
+ extend MBox
69
+ end
70
+ end
71
+
72
+ rescue MailboxFound
73
+ # Don't need to do anything--we only have to worry
74
+ # about it if there wasn't a mailbox there.
75
+ nil
76
+ end
77
+
78
+ begin
79
+ deliver_message(mailbox)
80
+ rescue SystemCallError
81
+ self.log "Gack! Something went wrong: " + $!.to_s
82
+ raise
83
+ # exit 75
84
+ end
85
+ end
86
+ end
87
+ end