rubymta 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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.