Module: Sisimai::SMTP::Transcript

Defined in:
lib/sisimai/smtp/transcript.rb

Overview

Sisimai::SMTP::Transcript is a decoder for the transcript logs of the SMTP session

Class Method Summary collapse

Class Method Details

.rise(argv0 = '', argv1 = '>>>', argv2 = '<<<') ⇒ Array

This method is abstract.

decodes the transcript of the SMTP session and makes structured data

Returns Structured data

Nil

Failed to decode or the 1st argument is missing.

Parameters:

  • argv0 (String) (defaults to: '')

    A transcript text MTA returned

  • argv1 (String) (defaults to: '>>>')

    A label string of a SMTP client

Returns:

  • (Array)

    Structured data

    Nil

    Failed to decode or the 1st argument is missing

Since:

  • v5.0.0



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/sisimai/smtp/transcript.rb', line 16

def rise(argv0 = '', argv1 = '>>>', argv2 = '<<<')
  return nil if argv0.size == 0

  # 1. Replace label strings of SMTP client/server at the each line
  argv0.gsub!(/^[ ]+#{argv1}\s+/m, '>>> '); return nil if argv0.include?('>>> ') == false
  argv0.gsub!(/^[ ]+#{argv2}\s+/m, '<<< '); return nil if argv0.include?('<<< ') == false

  # 2. Remove strings until the first '<<<' or '>>>'
  esmtp = []
  table = lambda do
    return {
      'command'   => nil, # SMTP command
      'argument'  => '',  # An argument of each SMTP command sent from a client
      'parameter' => {},  # Parameter pairs of the SMTP command
      'response'  => {    # A Response from an SMTP server
          'reply'  => '', # - SMTP reply code such as 550
          'status' => '', # - SMTP status such as 5.1.1
          'text'   => [], # - Response text lines
      }
    }
  end

  cv = ''
  cx = nil                      # Current session for esmtp
  p1 = argv0.index('>>>') || -1 # Sent command
  p2 = argv0.index('<<<') || -1 # Server response
  if p2 < p1
    # An SMTP server response starting with '<<<' is the first
    esmtp << table.call
    cx = esmtp[-1]
    cx['command'] = 'CONN'
    argv0 = argv0[p2, argv0.size] if p2 > -1
  else
    # An SMTP command starting with '>>>' is the first
    argv0 = argv0[p1, argv0.size] if p1 > -1
  end

  # 3. Remove unused lines, concatenate folded lines
  argv0 = argv0[0, argv0.index("\n\n") - 1] # Remove strings from the first blank line to the tail
  argv0.gsub!(/\n[ ]+/m, ' ')               # Concatenate folded lines to each previous line

  argv0.split("\n").each do |e|
    # 4. Read each SMTP command and server response
    if e.start_with?('>>> ')
      # SMTP client sent a command ">>> SMTP-command arguments"
      if cv = e.match(/\A>>>[ ]([A-Z]+)[ ]?(.*)\z/)
        # >>> SMTP Command
        thecommand = cv[1]
        commandarg = cv[2]
        parameters = ''

        esmtp << table.call
        cx = esmtp[-1]
        cx['command'] = thecommand.upcase

        if thecommand =~ /\A(?:MAIL|RCPT|XFORWARD)/
          # MAIL or RCPT
          if cv = commandarg.match(/\A(?:FROM|TO):[ ]*<(.+[@].+)>[ ]*(.*)\z/)
            # >>> MAIL FROM: <neko@example.com> SIZE=65535
            # >>> RCPT TO: <kijitora@example.org>
            cx['argument'] = cv[1]
            parameters = cv[2]

          else
            # >>> XFORWARD NAME=neko2-nyaan3.y.example.co.jp ADDR=230.0.113.2 PORT=53672
            # <<< 250 2.0.0 Ok
            # >>> XFORWARD PROTO=SMTP HELO=neko2-nyaan3.y.example.co.jp IDENT=2LYC6642BLzFK3MM SOURCE=REMOTE
            # <<< 250 2.0.0 Ok
            parameters = commandarg
            commandarg = ''
          end

          parameters.split(" ").each do |p|
            # SIZE=22022, PROTO=SMTP, and so on
            if cv = p.match(/\A([^ =]+)=([^ =]+)\z/) then cx['parameter'][cv[1].downcase] = cv[2] end
          end
        else
          # HELO, EHLO, AUTH, DATA, QUIT or Other SMTP command
          cx['argument'] = commandarg
        end
      end
    else
      # SMTP server sent a response "<<< response text"
      p = e.index('<<< '); next if p.nil? && p != 0
      e = e[4, e.size].sub(/\A<<<[ ]/, '')

      if cv = e.match(/\A([2-5]\d\d)[ ]/) then cx['response']['reply'] = cv[1] end
      if cv = e.match(/\A[245]\d\d[ ]([245][.]\d{1,3}[.]\d{1,3})[ ]/) then cx['response']['status'] = cv[1] end
      cx['response']['text'] << e
    end
  end

  return nil if esmtp.size == 0
  return esmtp
end