This file is indexed.

/usr/lib/ruby/1.8/rfilter/delivery_agent.rb is in librfilter-ruby1.8 0.12-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
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
351
352
353
354
355
356
357
358
359
360
#
#   Copyright (C) 2001, 2002, 2003 Matt Armstrong.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote
#    products derived from this software without specific prior
#    written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

require 'rmail/message'
require 'rmail/parser'
require 'rfilter/deliver'
require 'rfilter/mta'

module RFilter

  # The RFilter::DeliveryAgent class allows flexible delivery of
  # a mail message to a mailbox.  It is designed to make mail
  # filtering scripts easy to write, by allowing the filter author to
  # concentrate on the filter logic and not the particulars of the
  # message or folder format.
  #
  # It is designed primarily to work as an DeliveryAgent (local
  # delivery agent) for an SMTP server.  It should work well as the
  # basis for a script run from a .forward or .qmail file.
  class DeliveryAgent
    include RFilter::Deliver

    # A DeliveryComplete exception is one that indicates that
    # RFilter::DeliveryAgent's delivery process is now complete and.  The
    # DeliveryComplete exception is never thrown itself, but its
    # various subclasses are.
    class DeliveryComplete < StandardError
      # Create a new DeliveryComplete exception with a given +message+.
      def initialize(message)
	super
      end
    end

    # This exception is raised when there is a problem logging.
    class LoggingError < StandardError
      attr_reader :original_exception
      def initialize(message, original_exception = nil)
	super(message)
	@original_exception = original_exception
      end
    end

    # Raised when the command run by #pipe or #filter fails.
    class DeliveryCommandFailure < DeliveryComplete
      # This is the exit status of the pipe command.
      attr_reader :status
      def initialize(message, status)
	super(message)
	@status = status
      end
    end

    # Raised upon delivery success, unless the +continue+ flag of the
    # RFilter::DeliveryAgent delivery method was set to true.
    class DeliverySuccess < DeliveryComplete
      def initialize(message)
	super
      end
    end

    # Raised by RFilter::DeliveryAgent#reject.
    class DeliveryReject < DeliveryComplete
      def initialize(message)
	super
      end
    end

    # Raised by RFilter::DeliveryAgent#defer.
    class DeliveryDefer < DeliveryComplete
      def initialize(message)
	super
      end
    end

    # Create a new RFilter::DeliveryAgent object.
    #
    # +input+ may be a RMail::Message object (in which case,
    # it is used directly).  Otherwise, it is passed to
    # RMail::Message.new and used to create a new
    # RMail::Message object.
    #
    # +log+ may be nil (to disable logging completely) or a file name
    # to which log messages will be appended.
    def initialize(input, logfile)
      @logfile =
	if logfile.nil?
	  nil
	else
	  File.open(logfile, File::CREAT|File::APPEND|File::WRONLY, 0600)
	end
      @message = if input.is_a?(RMail::Message)
		   input
		 else
		   RMail::Parser.new.parse(input)
		 end
      @logging_level = 2
    end

    # Save this message to mail folder.  +folder+ must be the file
    # name of the mailbox.  If +folder+ ends in a slash (/) then the
    # mailbox will be considered to be in Maildir format, otherwise it
    # will be a Unix mbox folder.
    #
    # If +continue+ is false (the default), a
    # RFilter::DeliveryAgent::DeliverySuccess exception is raised upon
    # successful delivery.  Otherwise, the method simply returns upon
    # successful delivery.
    #
    # Upon failure, the function raises an exception as determined by
    # RFilter::Deliver.deliver_mbox or RFilter::Deliver.deliver_maildir.
    #
    # See also: RFilter::Deliver.deliver_mbox and
    # RFilter::Deliver.deliver_maildir.
    def save(folder, continue = false)
      log(2, "Action: save to #{folder.inspect}")
      retval = if folder =~ %r{(.*[^/])/$}
                 deliver_maildir($1, @message)
               else
                 deliver_mbox(folder, @message)
               end
      raise DeliverySuccess, "saved to mbox #{folder.inspect}" unless continue
      retval
    end

    # Reject this message.  Logs the +reason+ for the rejection and
    # raises a RFilter::DeliveryAgent::DeliveryReject exception.
    def reject(reason = nil)
      log(2, "Action: reject: " + reason.to_s)
      raise DeliveryReject.new(reason.to_s)
    end

    # Reject this message for now, but request that it be queued for
    # re-delivery in the future.  Logs the +reason+ for the rejection
    # and raises a RFilter::DeliveryAgent::DeliveryDefer exception.
    def defer(reason = nil)
      log(2, "Action: defer: " + reason.to_s)
      raise DeliveryDefer.new(reason.to_s)
    end

    # Pipe this message to a command.  +command+ must be a string
    # specifying a command to pipe the message to.
    #
    # If +continue+ is false, then a successful delivery to the pipe
    # will raise a RFilter::DeliveryAgent::DeliverySuccess exception.
    # If +continue+ is true, then a successful delivery will simply
    # return.  Regardless of +continue+, a failure to deliver to the
    # pipe will raise a RFilter::DeliveryAgent::DeliveryCommandFailure
    # exception.
    #
    # See also: RFilter::Deliver.deliver_pipe.
    def pipe(command, continue = false)
      log(2, "Action: pipe to #{command.inspect}")
      deliver_pipe(command, @message)
      if $? != 0
	m = "pipe failed for command #{command.inspect}"
        log(1, "Error: " + m)
	raise DeliveryCommandFailure.new(m, $?)
      end
      unless continue
	raise DeliverySuccess.new("pipe to #{command.inspect}")
      end
    end

    # Filter this message through a command.  +command+ must be a
    # string or an array of strings specifying a command to filter the
    # message through (it is passed to the Kernel::exec method).
    #
    # If the command does not exit with a status of 0, a
    # RFilter::DeliveryAgent::DeliveryCommandFailure exception is
    # raised and the current message is not replaced.
    #
    # See also: RFilter::Deliver.deliver_filter.
    def filter(*command)
      log(2, "Action: filter through #{command.inspect}")
      msg = nil
      status = deliver_filter(@message, *command) { |io|
        msg = RMail::Parser.new.parse(io)
      }
      if status != 0
	m = format("filter failed for command %s (status %s)",
                   command.inspect, status.inspect)
        log(1, "Error: " + m)
	raise DeliveryCommandFailure.new(m, status)
      end
      @message = msg
    end

    # Log a string to the log.  If the current log is nil or +level+
    # is greater than the current logging level, then the string will
    # not be logged.
    #
    # See also #logging_level, #logging_level=
    def log(level, str)
      if level <= 0 and @logfile.nil?
	raise LoggingError, "failed to log high priority message: #{str}"
      end
      return if @logfile.nil? or level > @logging_level
      begin
	@logfile.flock(File::LOCK_EX)
	@logfile.print(Time.now.strftime("%Y/%m/%d %H:%M:%S "))
	@logfile.print(sprintf("%05d: ", Process.pid))
	@logfile.puts(str)
	@logfile.flush
	@logfile.flock(File::LOCK_UN)
      rescue
	# FIXME: this isn't tested
	raise LoggingError.new("failed to log message: #{str}", $!)
      end
    end

    # Return the current logging level.
    #
    # See also: #logging_level=, #log
    def logging_level
      @logging_level
    end

    # Set the current logging level.  The +level+ must be a number no
    # less than one.
    #
    # See also: #logging_level, #log
    def logging_level=(level)
      level = Integer(level)
      raise ArgumentError, "invalid logging level value #{level}" if level < 1
      @logging_level = level
    end

    # Return the RMail::Message object associated with this
    # RFilter::DeliveryAgent.
    #
    # See also: #header, #body
    def message
      @message
    end

    # Sets the message (which should be a RMail::Message object) that
    # we're delivering.
    def message=(message)
      @message = message
    end

    # Return the header of the message as a RMail::Header object.
    # This is short hand for lda.message.header.
    #
    # See also: #message, #body
    def header
      @message.header
    end

    # Return the body of the message as an array of strings.  This is
    # short hand for lda.message.body.
    #
    # See also: #message, #header
    def body
      @message.body
    end

    class << self

      # Takes the same input as #new, but passes the created
      # RFilter::DeliveryAgent to the supplied block.  The idea
      # is that the entire delivery script is contained within the
      # block.
      #
      # This function tries to log exceptions that aren't
      # DeliveryComplete exceptions to the lda's log.  If it can log
      # them, it defers the delivery.  But if it can't, it re-raises
      # the exception so the caller can more properly deal with the
      # exception.
      #
      # Expected use:
      #
      #  begin
      #    RFilter::DeliveryAgent.process(stdin, "my-log-file") { |lda|
      #      # ...code uses lda to deliver mail...
      #    }
      #  rescue RFilter::DeliveryAgent::DeliveryComplete => exception
      #    exit(RFilter::DeliveryAgent.exitcode(exception))
      #  rescue Exception
      #    ... perhaps log the exception to a hard coded file ...
      #    exit(RFilter::MTA::EX_TEMPFAIL)
      #  end
      def process(input, logfile)
	begin
	  lda = RFilter::DeliveryAgent.new(input, logfile)
	  yield lda
	  lda.defer("finished without a final delivery")
	rescue Exception => exception
	  if exception.class <= DeliveryComplete
	    raise exception
	  else
	    begin
	      lda.log(0, "uncaught exception: " + exception.inspect)
	      lda.log(0, "uncaught exception backtrace:\n    " +
		      exception.backtrace.join("\n    "))
	      lda.defer("uncaught exception")
	    rescue Exception
	      if $!.class <= DeliveryComplete
		# The lda.defer above will generate this, just re-raise
		# the delivery status exception.
		raise
	      else
		# Any errors logging in the uncaught exception and we
		# just re-raise the original exception
		raise exception
	      end
	    end
	  end
	end
      end

      # This function expects the +exception+ argument to be a
      # RFilter::DeliveryAgent::DeliveryComplete subclass.  The function
      # will return the appropriate exitcode that the process should
      # exit with.
      def exitcode(exception)
	case exception
	when DeliverySuccess
	  RFilter::MTA::EXITCODE_DELIVERED
	when DeliveryReject
	  RFilter::MTA::EXITCODE_REJECT
	when DeliveryComplete
	  RFilter::MTA::EXITCODE_DEFER
	else
	  raise ArgumentError,
	    "argument is not a DeliveryComplete exception: " +
	    "#{exception.inspect} (#{exception.class})"
	end
      end

    end

  end
end