class RubyQmail::Queue

Constants

QMAIL_ERRORS
QMAIL_QUEUE_SUCCESS

Attributes

message[RW]
options[RW]
recipients[RW]
response[RW]
return_path[RW]
success[RW]

Public Class Methods

insert(return_path, recipients, message, *options) click to toggle source

Class Method to place the message into the Qmail queue.

# File lib/queue.rb, line 33
def self.insert(return_path, recipients, message, *options)
  q = Queue.new(return_path, recipients, message, *options)
  if q.options.has_key?[:ip] || q.options[:method]==:qmqp 
    q.qmqp
  else
    q.qmail_queue
  end
end
new(return_path=nil, recipients=nil, message=nil, *options) click to toggle source

Recipients can be a filename, array or other object that responds to :each, or to_s resolves to an email address Message can be a filename, string, or other object that responds to :each

# File lib/queue.rb, line 44
def initialize(return_path=nil, recipients=nil, message=nil, *options)
  parameters(return_path, recipients, message, options)
end

Public Instance Methods

qmail_remote(return_path=nil, recipients=nil, message=nil, *options) click to toggle source

Sends email directly via qmail-remote. It does not store in the queue, It will halt the process and wait for the network event to complete. If multiple recipients are passed, it will run qmail-remote delivery for each at a time to honor VERP return paths.

# File lib/queue.rb, line 139
def qmail_remote(return_path=nil, recipients=nil, message=nil, *options)
  parameters(return_path, recipients, message, options)
  rp1, rp2 = @return_path.split(/@/,2)
  rp = @return_path
  @recipients.each do |recip|
    unless @options[:noverp]
      mailbox, host = recip.split(/@/)
      rp = "#{rp1}#{mailbox}=#{host}@#{rp2}"
    end

    @message.rewind if @message.respond_to?(:rewind)
    cmd = "#{@options[:qmail_root]}+/bin/qmail-remote #{host} #{rp} #{recip}"
    @success = self.spawn_command(cmd) do |send, recv|
      @message.each { |m| send.puts m }
      send.close
      @response = recv.readpartial(1000)
    end

    @options[:logger].info("RubyQmail Remote #{recip} exited:#{@success} responded:#{@response}")
  end
  return [ @success, @response ] # Last one
end
qmqp(return_path=nil, recipients=nil, message=nil, *options) click to toggle source

Builds the QMQP request, and opens a connection to the QMQP Server and sends This implemtents the QMQP protocol, so does not need Qmail installed on the host system. System defaults will be used if no ip or port given. Returns true on success, false on failure (see @response), or nul on deferral

# File lib/queue.rb, line 92
def qmqp(return_path=nil, recipients=nil, message=nil, *options)
  parameters(return_path, recipients, message, options)
  
  begin
    ip = @options[:ip] || File.readlines(QMQP_SERVERS).first.chomp
    #puts "CONNECT #{:ip}, #{@options[:qmqp_port]}"
    socket = TCPSocket.new(ip, @options[:qmqp_port])
    raise "QMQP can not connect to #{ip}:#{@options[:qmqp_port]}" unless socket
    
    # Build netstring of messagebody+returnpath+recipient...
    nstr = (@message.map.join("\n")+"\n").to_netstring # { |m| m }.join("\t").to_netstring
    nstr += @return_path.to_netstring
    nstr += @recipients.map { |r| r.to_netstring }.join
    socket.send( nstr.to_netstring, 0 )

    @response = socket.recv(1000) # "23:Kok 1182362995 qp 21894," (its a netstring)
    @success = case @response.match(/^\d+:([KZD])(.+),$/)[1]
      when 'K' then true  # success
      when 'Z' then nil   # deferral
      when 'D' then false # failure
      else false
    end
    logmsg = "RubyQmail QMQP [#{ip}:#{@options[:qmqp_port]}]: #{@response} return:#{@success}"
    @options[:logger].info(logmsg)
    puts logmsg
    @success
  rescue Exception => e
    @options[:logger].error( "QMQP can not connect to #{@opt[:qmqp_ip]}:#{@options[:qmqp_port]} #{e}" )
    raise e
  ensure
    socket.close if socket
  end
end
queue(return_path=nil, recipients=nil, message=nil, *options) click to toggle source

This calls the Qmail-Queue program, so requires qmail to be installed (does not require it to be currently running).

# File lib/queue.rb, line 67
def queue(return_path=nil, recipients=nil, message=nil, *options)
  parameters(return_path, recipients, message, options)
  @success = run_qmail_queue() do |msg, env|
    # Send the Message
    @message.each { |m| msg.puts(m) }
    msg.close

    env.write('F' + @return_path + "\0")
    @recipients.each { |r| env.write('T' + r + "\0") }      
    env.write("\0") # End of "file"
  end
  @options[:logger].info("RubyQmail Queue exited:#{@success} #{Queue.qmail_queue_error_message(@success)}")
  return true if @success == QMAIL_QUEUE_SUCCESS
  raise Queue.qmail_queue_error_message(@success)
end
run_qmail_queue(command=nil) { |msg_write, env_write| ... } click to toggle source

Forks, sets up stdin and stdout pipes, and starts qmail-queue. IF a block is passed, yields to it with [sendpipe, receivepipe], and returns the exist cod, otherwise returns {:msg=>pipe, :env=>pipe, :pid=>@child} It exits 0 on success or another code on failure. Qmail-queue Protocol: Reads mail message from File Descriptor 0, then reads Envelope from FD 1 Envelope Stream: 'F' + sender_email + “0” + (“T” + recipient_email + “0”) … + “0”

# File lib/queue.rb, line 206
def run_qmail_queue(command=nil, &block)
  # Set up pipes and qmail-queue child process
  msg_read, msg_write = IO.pipe
  env_read, env_write = IO.pipe
  @child=fork # child? nil : childs_process_id

  unless @child 
    ## Set child's stdin(0) to read from msg
    $stdin.close # FD=0
    msg_read.dup
    msg_read.close
    msg_write.close

    ## Set child's stdout(1) to read from env
    $stdout.close # FD=1
    env_read.dup
    env_read.close
    env_write.close

    # Change directory and load command
    Dir.chdir(@options[:qmail_root])
    exec( command || @options[:qmail_queue] )
    raise "Exec qmail-queue failed"
  end

  # Parent Process with block
  if block_given?
    yield(msg_write, env_write)
  # msg_write.close
    env_write.close
    wait(@child)
    @success = $? >> 8
    # puts "#{$$} parent waited for #{@child} s=#{@success} #{$?.inspect}"
    return @sucess
  end
  
  # Parent process, no block
  {:msg=>msg_write, :env=>env_write, :pid=>@child}
end
spawn_command(command) { |parent_write, parent_read| ... } click to toggle source

Forks, sets up stdin and stdout pipes, and starts the command. IF a block is passed, yeilds to it with [sendpipe, receivepipe], returing the exit code, otherwise returns {:send=>, :recieve=>, :pid=>} qmail-queue does not work with this as it reads from both pipes.

# File lib/queue.rb, line 166
def spawn_command(command, &block)
  child_read, parent_write = IO.pipe # From parent to child(stdin)
  parent_read, child_write = IO.pipe # From child(stdout) to parent
  @child = fork

  # Child process
  unless @child # 
    $stdin.close # closes FD==0
    child_read.dup # copies to FD==0
    child_read.close
    
    $stdout.close # closes FD==1
    child_write.dup # copies to FD==1
    child_write.close

    Dir.chdir(@options[:qmail_root]) unless @options[:nochdir]
    exec(command)
    raise "Exec spawn_command #{command} failed"
  end
  
  # Parent Process with block
  if block_given?
    yield(parent_write, parent_read)
    parent_write.close
    parent_read.close
    wait(@child)
    @success = $? >> 8
    return @sucess
  end
  
  # Parent process, no block
  {:send=>parent_write, :receive=>parent_read, :pid=>@child}
end