rubymta 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4449c3d949eae74a0d181c0ed70dff9bc2077328
4
- data.tar.gz: a2c5a0c3875372065733b1ef4bc9c8ec841f9350
3
+ metadata.gz: 1601cb4b77cb390064b8a90aaec523a6bdf3c993
4
+ data.tar.gz: 764b1ccf31bfbb1b3386f9ec42a61510ee5a7edc
5
5
  SHA512:
6
- metadata.gz: 6efff36106006c5639cbb69aa3688bfeff46911b4550827e2ec778c33793d6a900f38a48b8be491b71b5f0af9186a566a7ff2b05362136ad470112cbf6c23561
7
- data.tar.gz: d3220cd027a8c4f40102374ebfcce0c6657f1d741f9e2b076e1c77a6f266dd4a4d606b8c179ea9c728da0e3e97038548fd4f800aa0f7bb35972242ef2f39f08b
6
+ metadata.gz: eda93abc4e139bec74e5ef868ff3ed2449832557c24948856158a51ad170ec0e0dde2e88feb81dadad1b6e71d160646ed09501d118bebbe14e018556255cb88f
7
+ data.tar.gz: f1c860ab3f0e7b44da1bc34109e6a2583a641521a30a64eed331894168ca05f42a831e6c0d652d6f0d16a445a88401d1b1eb27bf6a137b89a1605c0dabdfbb73
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # v0.0.10
2
+
3
+ #### Warning: This version changes the queue file format. Previous queue files will *not* be compatible with this version.
4
+
5
+ * Added a test for valid UTF-8 encoding. On a 777 character Greek text, a `valid_encoding?` takes only in 3.6ns, so checking every line of text is not expensive.
6
+ * Revised the way emails are stored on disk: the main structure has the mail[:data][:text] removed and stored following the main structure. The reason for this change is that `eval` doesn't handle foreign UTF-8 character sequences well.
7
+ - *__New Disk Format in `queue`:__*
8
+ - Number of lines in main mail structure;
9
+ - Main mail structure;
10
+ - Unmodified text; this is added back to the main structure at `mail[:data][:text]` after the queue file is read.
11
+ * Added a Quit at the moment the connecting IP is placed under prohibition. The reason for this is that some senders don't stop sending when they receive a 500+ level message.
12
+ * If there is a `parcel` record, but no matching disk file, the parcel record is marked as delivery='none'. This prevents the QueueRunner from looping when an undelivered queue file is manually deleted.
13
+ * Added checks at the end of delivery in QueueRunner to test for the message level (should be 200+ if accepted).
14
+ * Added another check for the client abruptly closing the connection.
15
+ * Added '=' to the list of acceptable characters in a local part of an email address.
16
+
1
17
  # v0.0.9
2
18
  * Added a `rescue OpenSSL::SSL::SSLError` to catch sender certificate violations.
3
19
  * Added a `LogLevel` into the Config file (config.rb) to control the logger output. I use LOGGER::INFO as my default.
@@ -60,6 +60,8 @@ class Contact < Hash
60
60
  if prohibited?
61
61
  self[:locks] += 1
62
62
  self[:expires_at] = Time.now + ProhibitedSeconds
63
+ modify
64
+ raise Quit # force quit: some senders try to keep going
63
65
  end
64
66
  modify
65
67
  end
@@ -87,13 +87,19 @@ class ItemOfMail < Hash
87
87
  end
88
88
 
89
89
  def save_mail_into_queue_folder
90
+ # split the ItemOfMail into a hash and a text (which may contain any UTF-8)
91
+ hash = self.dup
92
+ text = hash[:data].delete(:text)
90
93
  begin
91
- # save the mail in the Queue folder
94
+ # save the mail into the Queue folder
92
95
  File::open("#{MailQueue}/#{self[:mail_id]}","w") do |f|
93
- self[:saved] = true
94
- f.write(self.pretty_inspect)
96
+ tmp = hash.pretty_inspect
97
+ f.write("#{tmp.lines.count}\n")
98
+ f.write(tmp)
99
+ f.write("\n")
100
+ f.write(text.join("\n"))
95
101
  end
96
- return true
102
+ self[:saved] = true
97
103
  rescue => e
98
104
  LOG.error(self[:mail_id]) {e.to_s}
99
105
  e.backtrace.each { |line| LOG.error(self[:mail_id]) {line} }
@@ -104,14 +110,33 @@ class ItemOfMail < Hash
104
110
  def self::retrieve_mail_from_queue_folder(mail_id)
105
111
  item = nil
106
112
  begin
107
- mail = nil
108
- File::open("#{MailQueue}/#{mail_id}","r") do |f|
109
- mail = eval(f.read)
110
- item = ItemOfMail::new(mail)
113
+ # Make sure the file that matches the parcel record exists
114
+ if !File::exist?("#{MailQueue}/#{mail_id}")
115
+ # file missing: mark the parcels none and date them
116
+ parcels = S3DB[:parcels].where(:mail_id=>mail_id).all
117
+ parcels.each do |parcel|
118
+ S3DB[:parcels].where(:id=>parcel[:id]).update(:delivery=>'none', :delivery_at=>Time.now)
119
+ end
120
+ return nil
111
121
  end
122
+
123
+ tmp = nil; File::open("#{MailQueue}/#{mail_id}","r") { |f| tmp = f.read }
124
+ data = tmp.split("\n")
125
+
126
+ # get the number of lines in the hash, and cut that out first
127
+ n = data[0].to_i
128
+ a = data[1..n]
129
+ b = data[n+1..-1]
130
+
131
+ # convert the hash
132
+ mail = eval(a.join("\n"))
133
+
134
+ # create the ItemOfMail structure and insert the text
135
+ item = ItemOfMail::new(mail)
136
+ item[:data][:text] = b
112
137
  rescue => e
113
- LOG.error(self[:mail_id]) {e.to_s}
114
- e.backtrace.each { |line| LOG.error(self[:mail_id]) {line} }
138
+ LOG.error(mail_id) {e.to_s}
139
+ e.backtrace.each { |line| LOG.error(mail_id) {line} }
115
140
  end
116
141
  return item
117
142
  end
@@ -65,6 +65,7 @@ class QueueRunner
65
65
  # sqlite3 has a bug: "<=" doesn't work with time, ex. "retry_at<='#{Time.now}'"
66
66
  # we have to add 1 second and use "<"; ex. "retry_at<'#{Time.now+1}'"
67
67
  parcels = S3DB[:parcels].where(Sequel.lit("(delivery<>'none') and (delivery_at is null) and ((retry_at is null) or (retry_at<'#{Time.now + 1}'))")).all
68
+
68
69
  return if parcels.empty?
69
70
 
70
71
  # aggregate the emails by destination domain
@@ -81,21 +82,17 @@ class QueueRunner
81
82
  # handled in the respective mail routine
82
83
  mail = {}
83
84
  deliver.each do |mail_id, domains|
84
- (mail = ItemOfMail::retrieve_mail_from_queue_folder(mail_id)) if mail[:mail_id]!=mail_id
85
- domains.each do |domain, parcels|
85
+ (mail = ItemOfMail::retrieve_mail_from_queue_folder(mail_id)) if mail && mail[:mail_id]!=mail_id
86
86
 
87
- #=== compare before and after ======================================
88
- #puts "--> *2* domain=>#{domain.inspect}"
89
- #parcels.values.each { |parcel| puts "--> *3* #{parcel.inspect}" }
90
- #===================================================================
87
+ if mail.nil?
88
+ LOG.info(Time.now.strftime("%Y-%m-%d %H:%M:%S")) {"Skipping mail #{mail_id} because there's no associated queue file"}
89
+ next
90
+ end
91
91
 
92
+ domains.each do |domain, parcels|
92
93
  @mail_id = mail[:mail_id]
93
94
  deliver_and_save_status(mail, domain, parcels.values)
94
95
  @mail_id = nil
95
-
96
- #===================================================================
97
- #parcels.values.each { |parcel| puts "--> *4* #{parcel.inspect}" }
98
- #===================================================================
99
96
  end
100
97
  end
101
98
  if (n-=1)<0
@@ -289,7 +286,13 @@ class QueueRunner
289
286
 
290
287
  # get one final message for all parcels (recipients)
291
288
  ok, lines = recv_text
292
- return mark_parcels(parcels, lines)
289
+ if ok=='2'
290
+ ret = mark_parcels(parcels, lines)
291
+ LOG.info(@mail_id) {"Mail for #{parcels[0][:to_url]}, et.al. delivered remotely"}
292
+ else
293
+ LOG.info(@mail_id) {"Mail for #{parcels[0][:to_url]}, et.al. failed delivery remotely, #{lines.last}"}
294
+ end
295
+ return ret
293
296
  end
294
297
 
295
298
  # SAMPLE LMTP TRANSFER
@@ -371,7 +374,13 @@ class QueueRunner
371
374
  parcels.each do |parcel|
372
375
  # get the response from DoveCot
373
376
  ok, lines = recv_text
374
- mark_parcels([parcel], lines)
377
+ if ok=='2'
378
+ ret = mark_parcels([parcel], lines)
379
+ LOG.info(@mail_id) {"Mail for #{parcel[:to_url]} delivered locally"}
380
+ else
381
+ LOG.info(@mail_id) {"Mail for #{parcel[:to_url]} failed delivery locally, #{lines.last}"}
382
+ end
383
+ return ret
375
384
  end
376
385
  end
377
386
 
@@ -76,9 +76,14 @@ class Receiver
76
76
  Timeout.timeout(ReceiverTimeout) do
77
77
  begin
78
78
  temp = @connection.gets
79
- if temp.nil?
79
+ case
80
+ when temp.nil?
80
81
  LOG.warn(@mail[:mail_id]) {"The client abruptly closed the connection"}
81
82
  text = nil
83
+ when !temp.valid_encoding?
84
+ LOG.warn(@mail[:mail_id]) {"The client sent non-UTF-8 text"}
85
+ send_text("500 5.5.1 non-UTF-8 text detected")
86
+ raise Quit
82
87
  else
83
88
  text = temp.chomp
84
89
  end
@@ -138,7 +143,7 @@ class Receiver
138
143
  # Character . provided that it is not the first or last character,
139
144
  # and provided also that it does not appear two or more times consecutively.
140
145
  part[:dot_error] = true if (local_part[0]=='.' || local_part[-1]=='.' || local_part.index('..'))
141
- m = local_part.match(/^[a-zA-Z0-9\!\#\$%&'*+-\/?^_`{|}~]+$/)
146
+ m = local_part.match(/^[a-zA-Z0-9\!\#\$%&'*+-\/?^_`{|}~=]+$/)
142
147
  part[:char_error] = m.nil?
143
148
 
144
149
  # lookup the email to see if it's one of ours
@@ -352,7 +357,7 @@ class Receiver
352
357
  "550 5.1.7 beginning or ending '.' or 2 or more '.'s in a row" \
353
358
  if from[:dot_error]
354
359
  return "550-5.1.7 #{from[:local_part].inspect} can only", \
355
- "550 5.1.7 contain a-z, A_Z, 0-9, and !#\$%&'*+-/?^_`{|}~." \
360
+ "550 5.1.7 contain a-z, A_Z, 0-9, and !#\$%&'*+-/?^_`{|}~.=" \
356
361
  if from[:char_error]
357
362
 
358
363
  LOG.info(@mail[:mail_id]) {"Receiving mail from sender #{from[:url]}"}
@@ -1,5 +1,5 @@
1
1
  module Version
2
- VERSION = "0.0.9"
3
- MODIFIED = "2017-10-14"
2
+ VERSION = "0.0.10"
3
+ MODIFIED = "2017-10-18"
4
4
  end
5
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubymta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael J. Welch, Ph.D.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-14 00:00:00.000000000 Z
11
+ date: 2017-10-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: RubyMta is an experimental mail transport agent written in Ruby. See
14
14
  the README.