class As2::Server

Constants

HEADER_MAP

Attributes

logger[RW]

Public Class Methods

new(options = {}, &block) click to toggle source
# File lib/as2/server.rb, line 21
def initialize(options = {}, &block)
  @block = block
  @info = Config.server_info
  @options = options
end

Public Instance Methods

call(env) click to toggle source
# File lib/as2/server.rb, line 27
def call(env)
  if env['HTTP_AS2_TO'] != @info.name
    return send_error(env, "Invalid destination name #{env['HTTP_AS2_TO']}")
  end

  partner = Config.partners[env['HTTP_AS2_FROM']]
  unless partner
    return send_error(env, "Invalid partner name #{env['HTTP_AS2_FROM']}")
  end

  smime_string = build_smime_text(env)
  message = Message.new(smime_string, @info.pkey, @info.certificate)
  unless message.valid_signature?(partner.certificate)
    if @options[:on_signature_failure]
      @options[:on_signature_failure].call({env: env, smime_string: smime_string})
    else
      raise "Could not verify signature"
    end
  end

  mic = OpenSSL::Digest::SHA1.base64digest(message.decrypted_message)

  if @block
    begin
      @block.call message.attachment.filename, message.attachment.body
    rescue Exception => e
      return send_error(env, e.message)
    end
  end

  send_mdn(env, mic)
end

Private Instance Methods

build_smime_text(env) click to toggle source
# File lib/as2/server.rb, line 61
def build_smime_text(env)
  request = Rack::Request.new(env)
  smime_data = StringIO.new

  HEADER_MAP.each do |name, value|
    smime_data.puts "#{name}: #{env[value]}"
  end

  smime_data.puts 'Content-Transfer-Encoding: base64'
  smime_data.puts
  smime_data.puts Base64Helper.ensure_base64(request.body.read)

  return smime_data.string
end
send_error(env, msg) click to toggle source
# File lib/as2/server.rb, line 80
def send_error(env, msg)
  logger(env).error msg
  send_mdn env, nil, msg
end
send_mdn(env, mic, failed = nil) click to toggle source
# File lib/as2/server.rb, line 85
def send_mdn(env, mic, failed = nil)
  report = MimeGenerator::Part.new
  report['Content-Type'] = 'multipart/report; report-type=disposition-notification'

  text = MimeGenerator::Part.new
  text['Content-Type'] = 'text/plain'
  text['Content-Transfer-Encoding'] = '7bit'
  text.body = "The AS2 message has been received successfully"

  report.add_part text

  notification = MimeGenerator::Part.new
  notification['Content-Type'] = 'message/disposition-notification'
  notification['Content-Transfer-Encoding'] = '7bit'

  options = {
    'Reporting-UA' => @info.name,
    'Original-Recipient' => "rfc822; #{@info.name}",
    'Final-Recipient' => "rfc822; #{@info.name}",
    'Original-Message-ID' => env['HTTP_MESSAGE_ID']
  }
  if failed
    options['Disposition'] = 'automatic-action/MDN-sent-automatically; failed'
    options['Failure'] = failed
  else
    options['Disposition'] = 'automatic-action/MDN-sent-automatically; processed'
  end
  options['Received-Content-MIC'] = "#{mic}, sha1" if mic
  notification.body = options.map{|n, v| "#{n}: #{v}"}.join("\r\n")
  report.add_part notification

  msg_out = StringIO.new

  report.write msg_out

  pkcs7 = OpenSSL::PKCS7.sign @info.certificate, @info.pkey, msg_out.string
  pkcs7.detached = true
  smime_signed = OpenSSL::PKCS7.write_smime pkcs7, msg_out.string

  content_type = smime_signed[/^Content-Type: (.+?)$/m, 1]
  smime_signed.sub!(/\A.+?^(?=---)/m, '')

  headers = {}
  headers['Content-Type'] = content_type
  headers['MIME-Version'] = '1.0'
  headers['Message-ID'] = "<#{@info.name}-#{Time.now.strftime('%Y%m%d%H%M%S')}@#{@info.domain}>"
  headers['AS2-From'] = @info.name
  headers['AS2-To'] = env['HTTP_AS2_FROM']
  headers['AS2-Version'] = '1.2'
  headers['Connection'] = 'close'

  [200, headers, ["\r\n" + smime_signed]]
end