VvanGemert-ar_mailer 2.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,481 @@
1
+ require 'optparse'
2
+ require 'net/smtp'
3
+ require 'smtp_tls' unless Net::SMTP.instance_methods.include?("enable_starttls_auto")
4
+
5
+ ##
6
+ # Hack in RSET
7
+
8
+ module Net # :nodoc:
9
+ class SMTP # :nodoc:
10
+
11
+ unless instance_methods.include? 'reset' then
12
+ ##
13
+ # Resets the SMTP connection.
14
+
15
+ def reset
16
+ getok 'RSET'
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+
23
+ ##
24
+ # ActionMailer::ARSendmail delivers email from the email table to the
25
+ # SMTP server configured in your application's config/environment.rb.
26
+ # ar_sendmail does not work with sendmail delivery.
27
+ #
28
+ # ar_mailer can deliver to SMTP with TLS using smtp_tls.rb borrowed from Kyle
29
+ # Maxwell's action_mailer_optional_tls plugin. Simply set the :tls option in
30
+ # ActionMailer::Base's smtp_settings to true to enable TLS.
31
+ #
32
+ # See ar_sendmail -h for the full list of supported options.
33
+ #
34
+ # The interesting options are:
35
+ # * --daemon
36
+ # * --mailq
37
+
38
+ module ActionMailer; end
39
+
40
+ class ActionMailer::ARSendmail
41
+
42
+ ##
43
+ # The version of ActionMailer::ARSendmail you are running.
44
+
45
+ VERSION = '2.1.8'
46
+
47
+ ##
48
+ # Maximum number of times authentication will be consecutively retried
49
+
50
+ MAX_AUTH_FAILURES = 2
51
+
52
+ ##
53
+ # Email delivery attempts per run
54
+
55
+ attr_accessor :batch_size
56
+
57
+ ##
58
+ # Seconds to delay between runs
59
+
60
+ attr_accessor :delay
61
+
62
+ ##
63
+ # Maximum age of emails in seconds before they are removed from the queue.
64
+
65
+ attr_accessor :max_age
66
+
67
+ ##
68
+ # Be verbose
69
+
70
+ attr_accessor :verbose
71
+
72
+
73
+ ##
74
+ # True if only one delivery attempt will be made per call to run
75
+
76
+ attr_reader :once
77
+
78
+ ##
79
+ # Times authentication has failed
80
+
81
+ attr_accessor :failed_auth_count
82
+
83
+ @@pid_file = nil
84
+
85
+ def self.remove_pid_file
86
+ if @@pid_file
87
+ require 'shell'
88
+ sh = Shell.new
89
+ sh.rm @@pid_file
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Prints a list of unsent emails and the last delivery attempt, if any.
95
+ #
96
+ # If ActiveRecord::Timestamp is not being used the arrival time will not be
97
+ # known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
98
+ # to learn how to enable ActiveRecord::Timestamp.
99
+
100
+ def self.mailq
101
+ emails = ActionMailer::Base.email_class.all(:order => :priority)
102
+
103
+ if emails.empty? then
104
+ puts "Mail queue is empty"
105
+ return
106
+ end
107
+
108
+ total_size = 0
109
+
110
+ puts "-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------"
111
+ emails.each do |email|
112
+ size = email.mail.length
113
+ total_size += size
114
+
115
+ create_timestamp = email.created_at rescue
116
+ Time.at(email.created_date) rescue # for Robot Co-op
117
+ nil
118
+
119
+ created = if create_timestamp.nil? then
120
+ ' Unknown'
121
+ else
122
+ create_timestamp.strftime '%a %b %d %H:%M:%S'
123
+ end
124
+
125
+ puts "%10d %8d %s %s" % [email.id, size, created, email.from]
126
+ if email.last_send_attempt > 0 then
127
+ puts "Last send attempt: #{Time.at email.last_send_attempt}"
128
+ end
129
+ puts " #{email.to}"
130
+ puts
131
+ end
132
+
133
+ puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
134
+ end
135
+
136
+ ##
137
+ # Processes command line options in +args+
138
+
139
+ def self.process_args(args)
140
+ name = File.basename $0
141
+
142
+ options = {}
143
+ options[:Chdir] = '.'
144
+ options[:Daemon] = false
145
+ options[:Delay] = 60
146
+ options[:MaxAge] = 86400 * 7
147
+ options[:Once] = false
148
+ options[:RailsEnv] = ENV['RAILS_ENV']
149
+ options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
150
+
151
+ opts = OptionParser.new do |opts|
152
+ opts.banner = "Usage: #{name} [options]"
153
+ opts.separator ''
154
+
155
+ opts.separator "#{name} scans the email table for new messages and sends them to the"
156
+ opts.separator "website's configured SMTP host."
157
+ opts.separator ''
158
+ opts.separator "#{name} must be run from a Rails application's root."
159
+
160
+ opts.separator ''
161
+ opts.separator 'Sendmail options:'
162
+
163
+ opts.on("-b", "--batch-size BATCH_SIZE",
164
+ "Maximum number of emails to send per delay",
165
+ "Default: Deliver all available emails", Integer) do |batch_size|
166
+ options[:BatchSize] = batch_size
167
+ end
168
+
169
+ opts.on( "--delay DELAY",
170
+ "Delay between checks for new mail",
171
+ "in the database",
172
+ "Default: #{options[:Delay]}", Integer) do |delay|
173
+ options[:Delay] = delay
174
+ end
175
+
176
+ opts.on( "--max-age MAX_AGE",
177
+ "Maxmimum age for an email. After this",
178
+ "it will be removed from the queue.",
179
+ "Set to 0 to disable queue cleanup.",
180
+ "Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
181
+ options[:MaxAge] = max_age
182
+ end
183
+
184
+ opts.on("-o", "--once",
185
+ "Only check for new mail and deliver once",
186
+ "Default: #{options[:Once]}") do |once|
187
+ options[:Once] = once
188
+ end
189
+
190
+ opts.on("-d", "--daemonize",
191
+ "Run as a daemon process",
192
+ "Default: #{options[:Daemon]}") do |daemon|
193
+ options[:Daemon] = true
194
+ end
195
+
196
+ opts.on("-p", "--pidfile PIDFILE",
197
+ "Set the pidfile location",
198
+ "Default: #{options[:Chdir]}#{options[:Pidfile]}", String) do |pidfile|
199
+ options[:Pidfile] = pidfile
200
+ end
201
+
202
+ opts.on( "--mailq",
203
+ "Display a list of emails waiting to be sent") do |mailq|
204
+ options[:MailQ] = true
205
+ end
206
+
207
+ opts.separator ''
208
+ opts.separator 'Setup Options:'
209
+
210
+ opts.separator ''
211
+ opts.separator 'Generic Options:'
212
+
213
+ opts.on("-c", "--chdir PATH",
214
+ "Use PATH for the application path",
215
+ "Default: #{options[:Chdir]}") do |path|
216
+ usage opts, "#{path} is not a directory" unless File.directory? path
217
+ usage opts, "#{path} is not readable" unless File.readable? path
218
+ options[:Chdir] = path
219
+ end
220
+
221
+ opts.on("-e", "--environment RAILS_ENV",
222
+ "Set the RAILS_ENV constant",
223
+ "Default: #{options[:RailsEnv]}") do |env|
224
+ options[:RailsEnv] = env
225
+ end
226
+
227
+ opts.on("-v", "--[no-]verbose",
228
+ "Be verbose",
229
+ "Default: #{options[:Verbose]}") do |verbose|
230
+ options[:Verbose] = verbose
231
+ end
232
+
233
+ opts.on("-h", "--help",
234
+ "You're looking at it") do
235
+ usage opts
236
+ end
237
+
238
+ opts.on("--version", "Version of ARMailer") do
239
+ usage "ar_mailer #{VERSION} (adzap fork)"
240
+ end
241
+
242
+ opts.separator ''
243
+ end
244
+
245
+ opts.parse! args
246
+
247
+ ENV['RAILS_ENV'] = options[:RailsEnv]
248
+
249
+ Dir.chdir options[:Chdir] do
250
+ begin
251
+ require 'config/environment'
252
+ require 'action_mailer/ar_mailer'
253
+ rescue LoadError
254
+ usage opts, <<-EOF
255
+ #{name} must be run from a Rails application's root to deliver email.
256
+ #{Dir.pwd} does not appear to be a Rails application root.
257
+ EOF
258
+ end
259
+ end
260
+
261
+ return options
262
+ end
263
+
264
+ ##
265
+ # Processes +args+ and runs as appropriate
266
+
267
+ def self.run(args = ARGV)
268
+ options = process_args args
269
+
270
+ if options.include? :MailQ then
271
+ mailq
272
+ exit
273
+ end
274
+
275
+ if options[:Daemon] then
276
+ require 'webrick/server'
277
+ @@pid_file = File.expand_path(options[:Pidfile], options[:Chdir])
278
+ if File.exists? @@pid_file
279
+ # check to see if process is actually running
280
+ pid = ''
281
+ File.open(@@pid_file, 'r') {|f| pid = f.read.chomp }
282
+ if system("ps -p #{pid} | grep #{pid}") # returns true if process is running, o.w. false
283
+ $stderr.puts "Warning: The pid file #{@@pid_file} exists and ar_sendmail is running. Shutting down."
284
+ exit -1
285
+ else
286
+ # not running, so remove existing pid file and continue
287
+ self.remove_pid_file
288
+ $stderr.puts "ar_sendmail is not running. Removing existing pid file and starting up..."
289
+ end
290
+ end
291
+ WEBrick::Daemon.start
292
+ File.open(@@pid_file, 'w') {|f| f.write("#{Process.pid}\n")}
293
+ end
294
+
295
+ new(options).run
296
+
297
+ rescue SystemExit
298
+ raise
299
+ rescue SignalException
300
+ exit
301
+ rescue Exception => e
302
+ $stderr.puts "Unhandled exception #{e.message}(#{e.class}):"
303
+ $stderr.puts "\t#{e.backtrace.join "\n\t"}"
304
+ exit -2
305
+ end
306
+
307
+ ##
308
+ # Prints a usage message to $stderr using +opts+ and exits
309
+
310
+ def self.usage(opts, message = nil)
311
+ if message then
312
+ $stderr.puts message
313
+ $stderr.puts
314
+ end
315
+
316
+ $stderr.puts opts
317
+ exit 1
318
+ end
319
+
320
+ ##
321
+ # Creates a new ARSendmail.
322
+ #
323
+ # Valid options are:
324
+ # <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
325
+ # <tt>:Delay</tt>:: Delay between deliver attempts
326
+ # <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
327
+ # <tt>:Verbose</tt>:: Be verbose.
328
+
329
+ def initialize(options = {})
330
+ options[:Delay] ||= 60
331
+ options[:MaxAge] ||= 86400 * 7
332
+
333
+ @batch_size = options[:BatchSize]
334
+ @delay = options[:Delay]
335
+ @once = options[:Once]
336
+ @verbose = options[:Verbose]
337
+ @max_age = options[:MaxAge]
338
+
339
+ @failed_auth_count = 0
340
+ end
341
+
342
+ ##
343
+ # Removes emails that have lived in the queue for too long. If max_age is
344
+ # set to 0, no emails will be removed.
345
+
346
+ def cleanup
347
+ return if @max_age == 0
348
+ timeout = Time.now - @max_age
349
+ conditions = ['last_send_attempt > 0 and created_at < ?', timeout]
350
+ mail = ActionMailer::Base.email_class.destroy_all conditions
351
+
352
+ log "expired #{mail.length} emails from the queue"
353
+ end
354
+
355
+ ##
356
+ # Delivers +emails+ to ActionMailer's SMTP server and destroys them.
357
+
358
+ def deliver(emails)
359
+ settings = [
360
+ smtp_settings[:domain],
361
+ (smtp_settings[:user] || smtp_settings[:user_name]),
362
+ smtp_settings[:password],
363
+ smtp_settings[:authentication]
364
+ ]
365
+
366
+ smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
367
+ if smtp.respond_to?(:enable_starttls_auto)
368
+ smtp.enable_starttls_auto unless smtp_settings[:tls] == false
369
+ else
370
+ settings << smtp_settings[:tls]
371
+ end
372
+
373
+ smtp.start(*settings) do |session|
374
+ @failed_auth_count = 0
375
+ until emails.empty? do
376
+ email = emails.shift
377
+ begin
378
+ res = session.send_message email.mail, email.from, email.to
379
+ email.destroy
380
+ log "sent email %011d from %s to %s: %p" %
381
+ [email.id, email.from, email.to, res]
382
+ rescue Net::SMTPFatalError => e
383
+ log "5xx error sending email %d, removing from queue: %p(%s):\n\t%s" %
384
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
385
+ email.destroy
386
+ session.reset
387
+ rescue Net::SMTPServerBusy => e
388
+ log "server too busy, stopping delivery cycle"
389
+ return
390
+ rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError, Timeout::Error => e
391
+ email.last_send_attempt = Time.now.to_i
392
+ email.save rescue nil
393
+ log "error sending email %d: %p(%s):\n\t%s" %
394
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
395
+ session.reset
396
+ end
397
+ end
398
+ end
399
+ rescue Net::SMTPAuthenticationError => e
400
+ @failed_auth_count += 1
401
+ if @failed_auth_count >= MAX_AUTH_FAILURES then
402
+ log "authentication error, giving up: #{e.message}"
403
+ raise e
404
+ else
405
+ log "authentication error, retrying: #{e.message}"
406
+ end
407
+ sleep delay
408
+ rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
409
+ # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
410
+ end
411
+
412
+ ##
413
+ # Prepares ar_sendmail for exiting
414
+
415
+ def do_exit
416
+ log "caught signal, shutting down"
417
+ self.class.remove_pid_file
418
+ exit 130
419
+ end
420
+
421
+ ##
422
+ # Returns emails in email_class that haven't had a delivery attempt in the
423
+ # last 300 seconds.
424
+
425
+ def find_emails
426
+ options = { :conditions => ['last_send_attempt < ?', Time.now.to_i - 300], :order => :priority }
427
+ options[:limit] = batch_size unless batch_size.nil?
428
+ mail = ActionMailer::Base.email_class.find :all, options
429
+
430
+ log "found #{mail.length} emails to send"
431
+ mail
432
+ end
433
+
434
+ ##
435
+ # Installs signal handlers to gracefully exit.
436
+
437
+ def install_signal_handlers
438
+ trap 'TERM' do do_exit end
439
+ trap 'INT' do do_exit end
440
+ end
441
+
442
+ ##
443
+ # Logs +message+ if verbose
444
+
445
+ def log(message)
446
+ $stderr.puts message if @verbose
447
+ ActionMailer::Base.logger.info "ar_sendmail: #{message}"
448
+ end
449
+
450
+ ##
451
+ # Scans for emails and delivers them every delay seconds. Only returns if
452
+ # once is true.
453
+
454
+ def run
455
+ install_signal_handlers
456
+
457
+ loop do
458
+ begin
459
+ cleanup
460
+ emails = find_emails
461
+ deliver(emails) unless emails.empty?
462
+ rescue ActiveRecord::Transactions::TransactionError
463
+ end
464
+ break if @once
465
+ sleep @delay
466
+ end
467
+ end
468
+
469
+ ##
470
+ # Proxy to ActionMailer::Base::smtp_settings. See
471
+ # http://api.rubyonrails.org/classes/ActionMailer/Base.html
472
+ # for instructions on how to configure ActionMailer's SMTP server.
473
+ #
474
+ # Falls back to ::server_settings if ::smtp_settings doesn't exist for
475
+ # backwards compatibility.
476
+
477
+ def smtp_settings
478
+ ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
479
+ end
480
+
481
+ end
@@ -0,0 +1 @@
1
+ require 'action_mailer/ar_mailer'
data/lib/smtp_tls.rb ADDED
@@ -0,0 +1,105 @@
1
+ # Original code believed public domain from ruby-talk or ruby-core email.
2
+ # Modifications by Kyle Maxwell <kyle@kylemaxwell.com> used under MIT license.
3
+
4
+ require "openssl"
5
+ require "net/smtp"
6
+
7
+ # :stopdoc:
8
+
9
+ class Net::SMTP
10
+
11
+ class << self
12
+ send :remove_method, :start
13
+ end
14
+
15
+ def self.start( address, port = nil,
16
+ helo = 'localhost.localdomain',
17
+ user = nil, secret = nil, authtype = nil, use_tls = false,
18
+ &block) # :yield: smtp
19
+ new(address, port).start(helo, user, secret, authtype, use_tls, &block)
20
+ end
21
+
22
+ alias tls_old_start start
23
+
24
+ def start( helo = 'localhost.localdomain',
25
+ user = nil, secret = nil, authtype = nil, use_tls = false ) # :yield: smtp
26
+ start_method = use_tls ? :do_tls_start : :do_start
27
+ if block_given?
28
+ begin
29
+ send start_method, helo, user, secret, authtype
30
+ return yield(self)
31
+ ensure
32
+ do_finish
33
+ end
34
+ else
35
+ send start_method, helo, user, secret, authtype
36
+ return self
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def do_tls_start(helodomain, user, secret, authtype)
43
+ raise IOError, 'SMTP session already started' if @started
44
+ check_auth_args user, secret, authtype if user or secret
45
+
46
+ sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
47
+ @socket = Net::InternetMessageIO.new(sock)
48
+ @socket.read_timeout = 60 #@read_timeout
49
+ @socket.debug_output = STDERR #@debug_output
50
+
51
+ check_response(critical { recv_response() })
52
+ do_helo(helodomain)
53
+
54
+ raise 'openssl library not installed' unless defined?(OpenSSL)
55
+ starttls
56
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
57
+ ssl.sync_close = true
58
+ ssl.connect
59
+ @socket = Net::InternetMessageIO.new(ssl)
60
+ @socket.read_timeout = 60 #@read_timeout
61
+ @socket.debug_output = STDERR #@debug_output
62
+ do_helo(helodomain)
63
+
64
+ authenticate user, secret, authtype if user
65
+ @started = true
66
+ ensure
67
+ unless @started
68
+ # authentication failed, cancel connection.
69
+ @socket.close if not @started and @socket and not @socket.closed?
70
+ @socket = nil
71
+ end
72
+ end
73
+
74
+ def do_helo(helodomain)
75
+ begin
76
+ if @esmtp
77
+ ehlo helodomain
78
+ else
79
+ helo helodomain
80
+ end
81
+ rescue Net::ProtocolError
82
+ if @esmtp
83
+ @esmtp = false
84
+ @error_occured = false
85
+ retry
86
+ end
87
+ raise
88
+ end
89
+ end
90
+
91
+ def starttls
92
+ getok('STARTTLS')
93
+ end
94
+
95
+ alias tls_old_quit quit
96
+
97
+ def quit
98
+ begin
99
+ getok('QUIT')
100
+ rescue EOFError
101
+ end
102
+ end
103
+
104
+ end unless Net::SMTP.private_method_defined? :do_tls_start or
105
+ Net::SMTP.method_defined? :tls?
@@ -0,0 +1,30 @@
1
+ #!/bin/sh
2
+ # PROVIDE: ar_sendmail
3
+ # REQUIRE: DAEMON
4
+ # BEFORE: LOGIN
5
+ # KEYWORD: FreeBSD shutdown
6
+
7
+ #
8
+ # Add the following lines to /etc/rc.conf to enable ar_sendmail:
9
+ #
10
+ #ar_sendmail_enable="YES"
11
+
12
+ . /etc/rc.subr
13
+
14
+ name="ar_sendmail"
15
+ rcvar=`set_rcvar`
16
+
17
+ command="/usr/local/bin/ar_sendmail"
18
+ command_interpreter="/usr/local/bin/ruby18"
19
+
20
+ # set defaults
21
+
22
+ ar_sendmail_rails_env=${ar_sendmail_rails_env:-"production"}
23
+ ar_sendmail_chdir=${ar_sendmail_chdir:-"/"}
24
+ ar_sendmail_enable=${ar_sendmail_enable:-"NO"}
25
+ ar_sendmail_flags=${ar_sendmail_flags:-"-d"}
26
+
27
+ load_rc_config $name
28
+ export RAILS_ENV=$ar_sendmail_rails_env
29
+ run_rc_command "$1"
30
+
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # ar_sendmail Startup script for ar_mailer by Adam Meehan
4
+ #
5
+ # chkconfig: - 85 15
6
+ # description: ar_sendmail manages sending emails for Rails apps.
7
+ #
8
+ require 'yaml'
9
+
10
+ # Config file app mailers
11
+ config_file = '/etc/ar_sendmail.conf'
12
+
13
+ begin
14
+ config = YAML::load(IO.read(config_file)) || {}
15
+ if config.empty? || (config.has_key?('defaults') && config.size == 1)
16
+ puts "No mailers defined. Exiting."
17
+ exit -2
18
+ end
19
+ rescue Errno::ENOENT
20
+ puts "Config file not found at '#{config_file}'!"
21
+ exit -3
22
+ end
23
+
24
+ default_options = {'pidfile' => './log/ar_sendmail.pid'}.merge(config.delete('defaults') || {})
25
+
26
+ command, app_name = *ARGV
27
+
28
+ def start(app, options)
29
+ switches = ""
30
+ options.each {|k, v| switches << " --#{k} #{v}"}
31
+ STDOUT.write "Starting mailer for #{app} in #{options['environment']} mode ... "
32
+ status = system("ar_sendmail -d #{switches}") ? "started" : "failed"
33
+ puts status
34
+ end
35
+
36
+ def stop(app, options)
37
+ pid_file = File.expand_path(options['pidfile'], options['chdir'])
38
+ if File.exist? pid_file
39
+ begin
40
+ pid = open(pid_file).read.to_i
41
+ STDOUT.write "Stopping mailer for #{app}... "
42
+ Process.kill('TERM', pid)
43
+ puts "stopped"
44
+ rescue Errno::ESRCH
45
+ puts "Mailer process does not exist. Is not running."
46
+ end
47
+ else
48
+ puts "Skipping mailer for #{app}, no pid file."
49
+ end
50
+ end
51
+
52
+ def restart(app, options)
53
+ puts "Restarting mailer for #{app} ..."
54
+ stop app, options
55
+ start app, options
56
+ end
57
+
58
+ def command_error(msg)
59
+ puts msg
60
+ exit -1
61
+ end
62
+
63
+ if ['start', 'stop', 'restart'].include?(command)
64
+ apps = config
65
+ if app_name
66
+ command_error "No such app defined in ar_sendmail config" unless config.include?(app_name)
67
+ app_options = config[app_name]
68
+ command_error "Must specify chdir for app in ar_sendmail config" if app_options['chdir'].nil?
69
+ apps = {app_name => app_options}
70
+ end
71
+
72
+ apps.each do |app, options|
73
+ options = default_options.merge(options)
74
+ send(command, app, options)
75
+ end
76
+ else
77
+ command_error "Usage: ar_sendmail {start|stop|restart} [optional app_name]"
78
+ end