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 +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/rubymta/contact.rb +2 -0
- data/lib/rubymta/item_of_mail.rb +35 -10
- data/lib/rubymta/queue_runner.rb +21 -12
- data/lib/rubymta/receiver.rb +8 -3
- data/lib/rubymta/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1601cb4b77cb390064b8a90aaec523a6bdf3c993
|
4
|
+
data.tar.gz: 764b1ccf31bfbb1b3386f9ec42a61510ee5a7edc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/lib/rubymta/contact.rb
CHANGED
data/lib/rubymta/item_of_mail.rb
CHANGED
@@ -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
|
94
|
+
# save the mail into the Queue folder
|
92
95
|
File::open("#{MailQueue}/#{self[:mail_id]}","w") do |f|
|
93
|
-
|
94
|
-
f.write(
|
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
|
-
|
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
|
-
|
108
|
-
File::
|
109
|
-
|
110
|
-
|
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(
|
114
|
-
e.backtrace.each { |line| LOG.error(
|
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
|
data/lib/rubymta/queue_runner.rb
CHANGED
@@ -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
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/rubymta/receiver.rb
CHANGED
@@ -76,9 +76,14 @@ class Receiver
|
|
76
76
|
Timeout.timeout(ReceiverTimeout) do
|
77
77
|
begin
|
78
78
|
temp = @connection.gets
|
79
|
-
|
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]}"}
|
data/lib/rubymta/version.rb
CHANGED
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.
|
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-
|
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.
|