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

#after_complete, #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?, #receive_data, report_exception, #requires_authentication?, #reset_timeout!, #send_callback, #send_notification, #set_timer!, #start_tls, #time_remaning, #unbind, #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



248
249
250
251
252
# File 'lib/remailer/smtp/client.rb', line 248

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.



226
227
228
# File 'lib/remailer/smtp/client.rb', line 226

def after_proxy_connected
  use_smtp_interpreter!
end

#after_readyObject



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/remailer/smtp/client.rb', line 230

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



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/remailer/smtp/client.rb', line 131

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.



78
79
80
# File 'lib/remailer/smtp/client.rb', line 78

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

#closed?Boolean

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

Returns:

  • (Boolean)


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

def closed?
  !!@closed
end

#error?Boolean

Returns true if an error has occurred, false otherwise.

Returns:

  • (Boolean)


212
213
214
# File 'lib/remailer/smtp/client.rb', line 212

def error?
  !!@error
end

#pipelining?Boolean

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

Returns:

  • (Boolean)


196
197
198
# File 'lib/remailer/smtp/client.rb', line 196

def pipelining?
  !!@pipelining
end

#resolve_hostname(hostname) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/remailer/smtp/client.rb', line 175

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.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/remailer/smtp/client.rb', line 85

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.



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

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.



157
158
159
160
161
162
163
# File 'lib/remailer/smtp/client.rb', line 157

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.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/remailer/smtp/client.rb', line 110

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)


202
203
204
# File 'lib/remailer/smtp/client.rb', line 202

def tls_support?
  !!@tls_support
end

#unbound?Boolean

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

Returns:

  • (Boolean)


151
152
153
# File 'lib/remailer/smtp/client.rb', line 151

def unbound?
  !!@unbound
end

#use_smtp_interpreter!Object

Switches to use the SMTP interpreter for all subsequent communication



217
218
219
220
221
222
223
# File 'lib/remailer/smtp/client.rb', line 217

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

  # Trigger processing using the new interpreter as some SMTP data might
  # already be buffered by this point.
  receive_data
end

#use_socks5_interpreter!Object

Switches to use the SOCKS5 interpreter for all subsequent communication



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

def use_socks5_interpreter!
  @interpreter = Remailer::SOCKS5::Client::Interpreter.new(delegate: self)
end