Class: Remailer::SMTP::Client

Inherits:
AbstractConnection show all
Includes:
EventMachine::Deferrable, Constants
Defined in:
lib/remailer/smtp/client.rb

Defined Under Namespace

Classes: Interpreter

Constant Summary collapse

DEFAULT_TIMEOUT =
5

Constants included from Constants

Constants::CRLF, Constants::IMAPS_PORT, Constants::LINE_REGEXP, Constants::SMTP_PORT, Constants::SOCKS5_PORT

Constants inherited from AbstractConnection

AbstractConnection::NOTIFICATIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractConnection

#auth_support?, #cancel_timer!, #check_for_timeouts!, #close_connection, #connect_notification, #connected?, #connection_completed, #debug_notification, #error_notification, establish!, #initialize, #interpreter_entered_state, #message_callback, #post_init, #proxy_connection_initiated!, #proxy_connection_initiated?, report_exception, #requires_authentication?, #reset_timeout!, #send_callback, #send_notification, #set_timer!, #start_tls, #time_remaning, #unbind, #use_socks5_interpreter!, #use_tls?, #using_proxy?, warn_about_arguments

Constructor Details

This class inherits a constructor from Remailer::AbstractConnection

Instance Attribute Details

#active_messageObject

Properties ===========================================================



17
18
19
# File 'lib/remailer/smtp/client.rb', line 17

def active_message
  @active_message
end

#auth_supportObject

Returns the value of attribute auth_support.



19
20
21
# File 'lib/remailer/smtp/client.rb', line 19

def auth_support
  @auth_support
end

#errorObject (readonly)

Returns the value of attribute error.



22
23
24
# File 'lib/remailer/smtp/client.rb', line 22

def error
  @error
end

#error_messageObject (readonly)

Returns the value of attribute error_message.



22
23
24
# File 'lib/remailer/smtp/client.rb', line 22

def error_message
  @error_message
end

#hostnameObject

Returns the value of attribute hostname.



18
19
20
# File 'lib/remailer/smtp/client.rb', line 18

def hostname
  @hostname
end

#max_sizeObject

Returns the value of attribute max_size.



18
19
20
# File 'lib/remailer/smtp/client.rb', line 18

def max_size
  @max_size
end

#optionsObject

Returns the value of attribute options.



21
22
23
# File 'lib/remailer/smtp/client.rb', line 21

def options
  @options
end

#pipeliningObject

Returns the value of attribute pipelining.



19
20
21
# File 'lib/remailer/smtp/client.rb', line 19

def pipelining
  @pipelining
end

#protocolObject

Returns the value of attribute protocol.



18
19
20
# File 'lib/remailer/smtp/client.rb', line 18

def protocol
  @protocol
end

#remoteObject

Returns the value of attribute remote.



18
19
20
# File 'lib/remailer/smtp/client.rb', line 18

def remote
  @remote
end

#timeoutObject

Returns the value of attribute timeout.



20
21
22
# File 'lib/remailer/smtp/client.rb', line 20

def timeout
  @timeout
end

#tls_supportObject

Returns the value of attribute tls_support.



19
20
21
# File 'lib/remailer/smtp/client.rb', line 19

def tls_support
  @tls_support
end

Class Method Details

.default_portObject



34
35
36
# File 'lib/remailer/smtp/client.rb', line 34

def self.default_port
  SMTP_PORT
end

.default_timeoutObject

Class Methods ========================================================



30
31
32
# File 'lib/remailer/smtp/client.rb', line 30

def self.default_timeout
  DEFAULT_TIMEOUT
end

.open(smtp_server, options = nil, &block) ⇒ Object

Opens a connection to a specific SMTP server. Options can be specified:

  • port => Numerical port number (default is 25)

  • require_tls => If true will fail connections to non-TLS capable servers (default is false)

  • username => Username to authenticate with the SMTP server (optional)

  • password => Password to authenticate with the SMTP server (optional)

  • use_tls => Will use TLS if availble (default is true)

  • debug => Where to send debugging output (IO or Proc)

  • connect => Where to send a connection notification (IO or Proc)

  • error => Where to send errors (IO or Proc)

  • on_connect => Called upon successful connection (Proc)

  • on_error => Called upon connection error (Proc)

  • on_disconnect => Called when connection is closed (Proc)

A block can be supplied in which case it will stand in as the :connect option. The block will recieve a first argument that is the status of the connection, and an optional second that is a diagnostic message.



54
55
56
# File 'lib/remailer/smtp/client.rb', line 54

def self.open(smtp_server, options = nil, &block)
  super(smtp_server, options, &block)
end

Instance Method Details

#after_initializeObject

Called by AbstractConnection at the end of the initialize procedure



61
62
63
64
65
66
67
68
69
70
# File 'lib/remailer/smtp/client.rb', line 61

def after_initialize
  @protocol = :smtp

  if (using_proxy?)
    proxy_connection_initiated!
    use_socks5_interpreter!
  else
    use_smtp_interpreter!
  end
end

#after_message_sent(reply_code, reply_message) ⇒ Object



256
257
258
259
260
# File 'lib/remailer/smtp/client.rb', line 256

def after_message_sent(reply_code, reply_message)
  message_callback(reply_code, reply_message)

  @active_message = nil
end

#after_proxy_connectedObject

Callback receiver for when the proxy connection has been completed.



234
235
236
# File 'lib/remailer/smtp/client.rb', line 234

def after_proxy_connected
  use_smtp_interpreter!
end

#after_readyObject



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/remailer/smtp/client.rb', line 238

def after_ready
  super
  
  return if (@active_message)

  if (@active_message = @messages.shift)
    if (@interpreter.state == :ready)
      @interpreter.enter_state(:send)
    end
  elsif (@options[:close])
    if (callback = @options[:after_complete])
      callback.call
    end
    
    @interpreter.enter_state(:quit)
  end
end

#after_unbindObject



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/remailer/smtp/client.rb', line 126

def after_unbind
  if (@active_message)
    debug_notification(:disconnect, "Disconnected by remote before transaction could be completed.")

    if (callback = @active_message[:callback])
      callback.call(nil)

      @active_message = nil
    end
  elsif (@closed)
    debug_notification(:disconnect, "Disconnected from remote.")
  elsif (!@established)
    error_notification(:hangup, "Disconnected from remote before fully established.")
  else
    debug_notification(:disconnect, "Disconnected by remote while connection was idle.")
  end
end

#close_when_complete!Object

Closes the connection after all of the queued messages have been sent.



73
74
75
# File 'lib/remailer/smtp/client.rb', line 73

def close_when_complete!
  @options[:close] = true
end

#closed?Boolean

Returns true if the connection has been closed, false otherwise.

Returns:

  • (Boolean)


219
220
221
# File 'lib/remailer/smtp/client.rb', line 219

def closed?
  !!@closed
end

#error?Boolean

Returns true if an error has occurred, false otherwise.

Returns:

  • (Boolean)


224
225
226
# File 'lib/remailer/smtp/client.rb', line 224

def error?
  !!@error
end

#pipelining?Boolean

Returns true if pipelining support has been detected on the connection, false otherwise.

Returns:

  • (Boolean)


208
209
210
# File 'lib/remailer/smtp/client.rb', line 208

def pipelining?
  !!@pipelining
end

#receive_data(data) ⇒ Object

This implements the EventMachine::Connection#receive_data method that is called each time new data is received from the socket.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/remailer/smtp/client.rb', line 152

def receive_data(data)
  reset_timeout!

  @buffer ||= ''
  @buffer << data

  if (interpreter = @interpreter)
    interpreter.process(@buffer) do |reply|
      debug_notification(:receive, "[#{interpreter.label}] #{reply.inspect}")
    end
  else
    error_notification(:out_of_band, "Receiving data before a protocol has been established.")
  end
end

#resolve_hostname(hostname) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/remailer/smtp/client.rb', line 187

def resolve_hostname(hostname)
  record = Socket.gethostbyname(hostname)
  
  # FIXME: IPv6 Support here
  address = (record and record[3])
  
  if (address)
    debug_notification(:resolver, "Address #{hostname} resolved as #{address.unpack('CCCC').join('.')}")
  else
    debug_notification(:resolver, "Address #{hostname} could not be resolved")
  end
  
  yield(address) if (block_given?)

  address
rescue
  nil
end

#send_email(from, to, data, &block) ⇒ Object

Sends an email message through the connection at the earliest opportunity. A callback block can be supplied that will be executed when the message has been sent, an unexpected result occurred, or the send timed out.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/remailer/smtp/client.rb', line 80

def send_email(from, to, data, &block)
  if (block_given?)
    self.class.warn_about_arguments(block, 1..2)
  end
  
  message = {
    :from => from,
    :to => to,
    :data => data,
    :callback => block
  }
  
  @messages << message
  
  # If the connection is ready to send...
  if (@interpreter and @interpreter.state == :ready)
    # ...send the message right away.
    after_ready
  end
end

#send_line(line = '') ⇒ Object

Sends a single line to the remote host with the appropriate CR+LF delmiter at the end.



179
180
181
182
183
184
185
# File 'lib/remailer/smtp/client.rb', line 179

def send_line(line = '')
  reset_timeout!

  send_data(line + CRLF)

  debug_notification(:send, line.inspect)
end

#stateObject

Returns the current state of the active interpreter, or nil if no state is assigned.



169
170
171
172
173
174
175
# File 'lib/remailer/smtp/client.rb', line 169

def state
  if (interpreter = @interpreter)
    @interpreter.state
  else
    nil
  end
end

#test_email(from, to, &block) ⇒ Object

Tests the validity of an email address through the connection at the earliest opportunity. A callback block can be supplied that will be executed when the address has been tested, an unexpected result occurred, or the request timed out.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/remailer/smtp/client.rb', line 105

def test_email(from, to, &block)
  if (block_given?)
    self.class.warn_about_arguments(block, 1..2)
  end
  
  message = {
    :from => from,
    :to => to,
    :test => true,
    :callback => block
  }
  
  @messages << message
  
  # If the connection is ready to send...
  if (@interpreter and @interpreter.state == :ready)
    # ...send the message right away.
    after_ready
  end
end

#tls_support?Boolean

Returns true if pipelining support has been detected on the connection, false otherwise.

Returns:

  • (Boolean)


214
215
216
# File 'lib/remailer/smtp/client.rb', line 214

def tls_support?
  !!@tls_support
end

#unbound?Boolean

Returns true if the connection has been unbound by EventMachine, false otherwise.

Returns:

  • (Boolean)


146
147
148
# File 'lib/remailer/smtp/client.rb', line 146

def unbound?
  !!@unbound
end

#use_smtp_interpreter!Object

Switches to use the SMTP interpreter for all subsequent communication



229
230
231
# File 'lib/remailer/smtp/client.rb', line 229

def use_smtp_interpreter!
  @interpreter = Interpreter.new(:delegate => self)
end