Class: FakeSMTPd::Server

Inherits:
GServer
  • Object
show all
Defined in:
lib/fakesmtpd/server.rb

Constant Summary collapse

VERSION =
'0.3.1'
USAGE =
<<-EOU.gsub(/^ {6}/, '')
  Usage: #{File.basename($0)} <smtp-port> <message-dir> [options]

  The `<smtp-port>` argument will be incremented by 1 for the HTTP API port.
  The `<message-dir>` is where each SMTP transaction will be written as a
  JSON file containing the "smtp client id" (timestamp from beginning of SMTP
  transaction), the sender, recipients, and combined headers and body as
  an array of strings.

  Version: #{VERSION}

EOU

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Server

Returns a new instance of Server.



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/fakesmtpd/server.rb', line 261

def initialize(options = {})
  @port = options.fetch(:port)
  @message_dir = options.fetch(:dir)
  @pidfile = options[:pidfile]
  @messages = MessageStore.new(@message_dir)

  super(
    @port,
    options[:host] || '0.0.0.0',
    options[:max_connections] || 4,
    options[:logfile],
    options[:audit] || !!ENV['FAKESMTPD_AUDIT'] || false,
    options[:debug] || !!ENV['FAKESMTPD_DEBUG'] || false
  )
end

Instance Attribute Details

#logfileObject (readonly)

Returns the value of attribute logfile.



202
203
204
# File 'lib/fakesmtpd/server.rb', line 202

def logfile
  @logfile
end

#message_dirObject (readonly)

Returns the value of attribute message_dir.



202
203
204
# File 'lib/fakesmtpd/server.rb', line 202

def message_dir
  @message_dir
end

#messagesObject (readonly)

Returns the value of attribute messages.



203
204
205
# File 'lib/fakesmtpd/server.rb', line 203

def messages
  @messages
end

#pidfileObject (readonly)

Returns the value of attribute pidfile.



202
203
204
# File 'lib/fakesmtpd/server.rb', line 202

def pidfile
  @pidfile
end

#portObject (readonly)

Returns the value of attribute port.



202
203
204
# File 'lib/fakesmtpd/server.rb', line 202

def port
  @port
end

Class Method Details

.main(argv = []) ⇒ Object



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
# File 'lib/fakesmtpd/server.rb', line 206

def main(argv = [])
  options = {
    pidfile: nil,
    logfile: $stderr.set_encoding('UTF-8'),
  }

  OptionParser.new do |opts|
    opts.banner = USAGE
    opts.on('--version', 'Show version and exit') do |*|
      puts "fakesmtpd #{FakeSMTPd::Server::VERSION}"
      exit 0
    end
    opts.on('-p PIDFILE', '--pidfile PIDFILE',
            'Optional file where process PID will be written') do |pidfile|
      options[:pidfile] = pidfile
    end
    opts.on('-l LOGFILE', '--logfile LOGFILE',
            'Optional file where all log messages will be written ' <<
            '(default $stderr)') do |logfile|
      options[:logfile] = File.open(logfile, 'a:UTF-8')
    end
  end.parse!(argv)

  unless argv.length == 2
    abort USAGE
  end

  @smtpd = FakeSMTPd::Server.new(
    port: Integer(argv.fetch(0)),
    dir: argv.fetch(1),
    pidfile: options[:pidfile],
    logfile: options[:logfile],
  )
  @httpd = FakeSMTPd::HTTPServer.new(
    port: Integer(argv.fetch(0)) + 1,
    smtpd: @smtpd,
    logfile: options[:logfile],
  )

  @httpd.start
  @smtpd.start
  @httpd.join && @smtpd.join
rescue Exception => e
  if @httpd && !@httpd.stopped?
    @httpd.stop
  end
  if @smtpd && !@smtpd.stopped?
    @smtpd.stop
  end
  unless e.is_a?(Interrupt)
    raise e
  end
end

Instance Method Details

#record(client, from, recipients, body) ⇒ Object



356
357
358
359
360
# File 'lib/fakesmtpd/server.rb', line 356

def record(client, from, recipients, body)
  messages.store(
    client.client_id, from, recipients, body
  )
end

#serve(client) ⇒ Object



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
# File 'lib/fakesmtpd/server.rb', line 292

def serve(client)
  client.set_encoding('UTF-8')

  class << client
    attr_reader :client_id

    def getline
      line = gets
      line.chomp! unless line.nil?
      line
    end

    def to_s
      @client_id ||= Time.now.utc.strftime('%Y%m%d%H%M%S%N')
      "<smtp client #{@client_id}>"
    end
  end

  client.puts '220 localhost fakesmtpd ready ESMTP'
  helo = client.getline
  log "#{client} Helo: #{helo.inspect}"

  if helo =~ /^EHLO\s+/
    log "#{client} Seen an EHLO"
    client.puts '250-localhost only has this one extension'
    client.puts '250 HELP'
  end

  from = client.getline
  client.puts '250 OK'
  log "#{client} From: #{from.inspect}"

  recipients = []
  loop do
    to = client.getline
    break if to.nil?

    if to =~ /^DATA/
      client.puts '354 Lemme have it'
      break
    else
      log "#{client} To: #{to.inspect}"
      recipients << to
      client.puts '250 OK'
    end
  end

  lines = []
  loop do
    line = client.getline
    break if line.nil? || line == '.'
    lines << line
    log "#{client} + #{line}"
  end

  client.puts '250 OK'
  client.gets
  client.puts '221 Buhbye'
  client.close
  log "#{client} ding!"

  record(client, from, recipients, lines)
end

#start(*args) ⇒ Object



277
278
279
280
281
282
283
284
285
# File 'lib/fakesmtpd/server.rb', line 277

def start(*args)
  super(*args)
  if pidfile
    File.open(pidfile, 'w') { |f| f.puts($$) }
  end
  log "FakeSMTPd SMTP server serving on #{port}, writing messages to " <<
      "#{message_dir.inspect}"
  log "PID=#{$$} Thread=#{Thread.current.inspect}"
end

#stop(*args) ⇒ Object



287
288
289
290
# File 'lib/fakesmtpd/server.rb', line 287

def stop(*args)
  log "FakeSMTPd SMTP server stopping"
  super(*args)
end