schleuder 3.5.3 → 3.6.0
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/README.md +6 -5
- data/Rakefile +3 -1
- data/db/migrate/20200118170110_add_set_reply_to_to_sender_and_munge_from.rb +15 -0
- data/db/schema.rb +3 -1
- data/etc/list-defaults.yml +18 -0
- data/lib/schleuder/conf.rb +7 -1
- data/lib/schleuder/list.rb +17 -1
- data/lib/schleuder/mail/message.rb +83 -9
- data/lib/schleuder/subscription.rb +27 -4
- data/lib/schleuder/version.rb +1 -1
- data/locales/de.yml +1 -0
- data/locales/en.yml +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 648f81aa424a3214a9d03fb66e5b3e216c8990e092d1b5b18ab41a1f0a1e5fd3
|
4
|
+
data.tar.gz: 62a5a77189860b4228a2168690f3990d02e53ee4dfb6c458a0273b5407e8a2e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97a79b18f6655bd6f9c56beded7527e061fd955433ac2e436a28176eef9d8903916b16a1b0ee22a51f600fbd56d4639b5fadd5a53f3df08d4ca156fc805c9cbb
|
7
|
+
data.tar.gz: 6e6b4ea2847974d64a3b38a0d99ec0bfa5bb824a36d566fa731889a711b88f127ad6314f9f44b8dd99937977dc0f9d9f2530419762fae527bb24e37b10517343
|
data/README.md
CHANGED
@@ -15,6 +15,7 @@ Requirements
|
|
15
15
|
* gpgme
|
16
16
|
* sqlite3
|
17
17
|
* openssl
|
18
|
+
* icu
|
18
19
|
|
19
20
|
*If you use Debian buster or CentOS 7, please have a look at the [installation docs](https://schleuder.org/schleuder/docs/server-admins.html#installation). We do provide packages for those platforms, which simplify the installation a lot.*
|
20
21
|
|
@@ -22,7 +23,7 @@ Requirements
|
|
22
23
|
|
23
24
|
On systems that base on Debian 10 ("buster"), install the dependencies via
|
24
25
|
|
25
|
-
apt-get install ruby-dev gnupg2 libgpgme-dev libsqlite3-dev libssl-dev build-essential
|
26
|
+
apt-get install ruby-dev gnupg2 libgpgme-dev libsqlite3-dev libssl-dev build-essential libicu-dev
|
26
27
|
|
27
28
|
|
28
29
|
We **recommend** to also run a random number generator like [haveged](http://www.issihosts.com/haveged/). This ensures Schleuder won't be blocked by lacking entropy, which otherwise might happen especially during key generation.
|
@@ -47,15 +48,15 @@ Additionally these **rubygems** are required (will be installed automatically un
|
|
47
48
|
Installing Schleuder
|
48
49
|
------------
|
49
50
|
|
50
|
-
1. Download [the gem](https://schleuder.org/download/schleuder-3.
|
51
|
+
1. Download [the gem](https://schleuder.org/download/schleuder-3.6.0.gem) and [the OpenPGP-signature](https://schleuder.org/download/schleuder-3.6.0.gem.sig) and verify:
|
51
52
|
```
|
52
53
|
gpg --recv-key 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3
|
53
|
-
gpg --verify schleuder-3.
|
54
|
+
gpg --verify schleuder-3.6.0.gem.sig
|
54
55
|
```
|
55
56
|
|
56
57
|
2. If all went well install the gem:
|
57
58
|
```
|
58
|
-
gem install schleuder-3.
|
59
|
+
gem install schleuder-3.6.0.gem
|
59
60
|
```
|
60
61
|
|
61
62
|
3. Set up schleuder:
|
@@ -145,4 +146,4 @@ GNU GPL 3.0. Please see [LICENSE.txt](LICENSE.txt).
|
|
145
146
|
Alternative Download
|
146
147
|
--------------------
|
147
148
|
|
148
|
-
Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-3.
|
149
|
+
Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-3.6.0.tar.gz) and [its OpenPGP-signature](https://schleuder.org/download/schleuder-3.6.0.tar.gz.sig).
|
data/Rakefile
CHANGED
@@ -9,6 +9,8 @@ require_relative "lib/#{project}.rb"
|
|
9
9
|
|
10
10
|
load "active_record/railties/databases.rake"
|
11
11
|
|
12
|
+
puts @filename_gem
|
13
|
+
|
12
14
|
# Configure ActiveRecord
|
13
15
|
ActiveRecord::Tasks::DatabaseTasks.tap do |config|
|
14
16
|
config.root = File.dirname(__FILE__)
|
@@ -89,7 +91,7 @@ end
|
|
89
91
|
|
90
92
|
desc 'OpenPGP-sign gem and tarball'
|
91
93
|
task :sign_tarball do
|
92
|
-
`gpg -u #{@gpguid} -b #{@filename_tarball}`
|
94
|
+
`gpg -v -u #{@gpguid} -b #{@filename_tarball}`
|
93
95
|
end
|
94
96
|
|
95
97
|
desc 'OpenPGP-sign gem'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class AddSetReplyToToSenderAndMungeFrom < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if ! column_exists?(:lists, :set_reply_to_to_sender)
|
4
|
+
add_column :lists, :set_reply_to_to_sender, :boolean, default: false
|
5
|
+
end
|
6
|
+
if ! column_exists?(:lists, :munge_from)
|
7
|
+
add_column :lists, :munge_from, :boolean, default: false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def down
|
12
|
+
remove_column(:lists, :set_reply_to_to_sender)
|
13
|
+
remove_column(:lists, :munge_from)
|
14
|
+
end
|
15
|
+
end
|
data/db/schema.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20200118170110) do
|
15
15
|
|
16
16
|
create_table "lists", force: :cascade do |t|
|
17
17
|
t.datetime "created_at"
|
@@ -46,6 +46,8 @@ ActiveRecord::Schema.define(version: 20190906194820) do
|
|
46
46
|
t.integer "logfiles_to_keep", default: 2
|
47
47
|
t.text "internal_footer", default: ""
|
48
48
|
t.boolean "include_autocrypt_header", default: true
|
49
|
+
t.boolean "set_reply_to_to_sender", default: false
|
50
|
+
t.boolean "munge_from", default: false
|
49
51
|
end
|
50
52
|
|
51
53
|
create_table "subscriptions", force: :cascade do |t|
|
data/etc/list-defaults.yml
CHANGED
@@ -131,3 +131,21 @@ forward_all_incoming_to_admins: false
|
|
131
131
|
# Disabling this only works for signed e-mails; any e-mail that is unsigned
|
132
132
|
# sent to the list is treated as coming from an unknown source
|
133
133
|
deliver_selfsent: true
|
134
|
+
|
135
|
+
# Set reply-to header to original sender's reply-to?
|
136
|
+
# Enabling this will set the reply-to-header of emails sent by schleuder
|
137
|
+
# to the original sender's reply-to-header. If the original sender
|
138
|
+
# did not supply a reply-to-header, the original from-header will be used.
|
139
|
+
# This option can enabled for improved usability since this affect
|
140
|
+
# mail client's reply-to button to reply to the original sender instead of
|
141
|
+
# the whole list.
|
142
|
+
set_reply_to_to_sender: false
|
143
|
+
|
144
|
+
# Munge from-header?
|
145
|
+
# Enabling this option will add the original sender to the from-header.
|
146
|
+
# To avoid DMARC issues, we still use the list's address as from-address.
|
147
|
+
# However the sender's address will be included as displayed name.
|
148
|
+
# For example: "sender@sender.org via list@list.org" <list@list.org>
|
149
|
+
# This option can enabled for improved usability since this affect
|
150
|
+
# mail client's displayed name.
|
151
|
+
munge_from: false
|
data/lib/schleuder/conf.rb
CHANGED
@@ -4,7 +4,13 @@ module Schleuder
|
|
4
4
|
class Conf
|
5
5
|
include Singleton
|
6
6
|
|
7
|
-
|
7
|
+
# since the regexp got only included into stdlib 2.2
|
8
|
+
# TODO: remove once 2.1 support dropped
|
9
|
+
if RUBY_VERSION < '2.2'
|
10
|
+
EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/
|
11
|
+
else
|
12
|
+
EMAIL_REGEXP = URI::MailTo::EMAIL_REGEXP
|
13
|
+
end
|
8
14
|
# TODO: drop v3 keys and only accept length of 40
|
9
15
|
FINGERPRINT_REGEXP = /\A(0x)?[a-f0-9]{32}([a-f0-9]{8})?\z/i
|
10
16
|
|
data/lib/schleuder/list.rb
CHANGED
@@ -67,6 +67,22 @@ module Schleuder
|
|
67
67
|
with: /\A[[:graph:]\s]*\z/i,
|
68
68
|
}
|
69
69
|
|
70
|
+
# Some users find it quite confusing when they click "reply-to" and the mail client
|
71
|
+
# doesn't reply to the sender of the mail but the whole mailing list. For those lists it can be
|
72
|
+
# considered to set this value to true. The recipients will then receive e-mails
|
73
|
+
# where the "reply-to" header will contain the reply-to address
|
74
|
+
# of the sender and thus reply to the sender when clicking "reply-to" in a client.
|
75
|
+
# If no "reply-to" is set, the "from"-header of the original sender will be used.
|
76
|
+
# The default is off.
|
77
|
+
validates :set_reply_to_to_sender, boolean: true
|
78
|
+
|
79
|
+
# Some users find it confusing when the "from" does not contain the original sender
|
80
|
+
# but the list address. For those lists it can be considered to set the munged header.
|
81
|
+
# This will result in a "from"-header like this: "originalsender@original.com via list@list.com"
|
82
|
+
# The default is off.
|
83
|
+
validates :munge_from, boolean: true
|
84
|
+
|
85
|
+
|
70
86
|
default_scope { order(:email) }
|
71
87
|
|
72
88
|
def self.configurable_attributes
|
@@ -367,7 +383,7 @@ module Schleuder
|
|
367
383
|
next
|
368
384
|
end
|
369
385
|
|
370
|
-
subscription.send_mail(mail)
|
386
|
+
subscription.send_mail(mail, incoming_mail)
|
371
387
|
|
372
388
|
rescue => exc
|
373
389
|
msg = I18n.t('errors.delivery_error',
|
@@ -205,17 +205,17 @@ module Mail
|
|
205
205
|
@recipient.match(/-bounce@/).present? ||
|
206
206
|
# Empty Return-Path
|
207
207
|
self.return_path.to_s == '<>' ||
|
208
|
-
|
209
|
-
# - no cron header is present
|
210
|
-
# - no Jenkins job notification header is present
|
211
|
-
# as these emails have the auto-submitted header.
|
212
|
-
( self['Auto-Submitted'].present? && \
|
213
|
-
self['Auto-Submitted'].to_s.downcase != 'no' && \
|
214
|
-
!self['X-Cron-Env'].present? && \
|
215
|
-
!self['X-Jenkins-Job'].present? && \
|
216
|
-
self.subject.to_s !~ /\A\*\*\* SECURITY information.*\*\*\*\Z/)
|
208
|
+
bounced?
|
217
209
|
end
|
218
210
|
|
211
|
+
def bounced?
|
212
|
+
@bounced ||= bounce_detected? || (error_status != "unknown")
|
213
|
+
end
|
214
|
+
|
215
|
+
def error_status
|
216
|
+
@error_status ||= detect_error_code
|
217
|
+
end
|
218
|
+
|
219
219
|
def keywords
|
220
220
|
return @keywords if @keywords
|
221
221
|
|
@@ -526,5 +526,79 @@ module Mail
|
|
526
526
|
end
|
527
527
|
end.join(' ')
|
528
528
|
end
|
529
|
+
|
530
|
+
def detect_error_code
|
531
|
+
# Detects the error code of an email with different heuristics
|
532
|
+
# from: https://github.com/mailtop/bounce_email
|
533
|
+
|
534
|
+
# Custom status codes
|
535
|
+
unicode_subject = self.subject.to_s
|
536
|
+
unicode_subject = unicode_subject.encode('utf-8') if unicode_subject.respond_to?(:encode)
|
537
|
+
|
538
|
+
return '97' if unicode_subject.match(/delayed/i)
|
539
|
+
return '98' if unicode_subject.match(/(unzulässiger|unerlaubter) anhang/i)
|
540
|
+
return '99' if unicode_subject.match(/auto.*reply|férias|ferias|Estarei ausente|estou ausente|vacation|vocation|(out|away).*office|on holiday|abwesenheits|autorespond|Automatische|eingangsbestätigung/i)
|
541
|
+
|
542
|
+
# Feedback-Type: abuse
|
543
|
+
return '96' if self.to_s.match(/Feedback-Type\: abuse/i)
|
544
|
+
|
545
|
+
if self.parts[1]
|
546
|
+
match_parts = self.parts[1].body.match(/(Status:.|550 |#)([245]\.[0-9]{1,3}\.[0-9]{1,3})/)
|
547
|
+
code = match_parts[2] if match_parts
|
548
|
+
return code if code
|
549
|
+
end
|
550
|
+
|
551
|
+
# Now try getting it from correct part of tmail
|
552
|
+
code = detect_bounce_status_code_from_text(self.body)
|
553
|
+
return code if code
|
554
|
+
|
555
|
+
# OK getting desperate so try getting code from entire email
|
556
|
+
code = detect_bounce_status_code_from_text(self.to_s)
|
557
|
+
code || 'unknown'
|
558
|
+
end
|
559
|
+
|
560
|
+
def bounce_detected?
|
561
|
+
# Detects bounces from different parts of the email without error status codes
|
562
|
+
# from: https://github.com/mailtop/bounce_email
|
563
|
+
return true if self.subject.to_s.match(/(returned|undelivered) mail|mail delivery( failed)?|(delivery )(status notification|failure)|failure notice|undeliver(able|ed)( mail)?|return(ing message|ed) to sender/i)
|
564
|
+
return true if self.subject.to_s.match(/auto.*reply|vacation|vocation|(out|away).*office|on holiday|abwesenheits|autorespond|Automatische|eingangsbestätigung/i)
|
565
|
+
return true if self['precedence'].to_s.match(/auto.*(reply|responder|antwort)/i)
|
566
|
+
return true if self.from.to_s.match(/^(MAILER-DAEMON|POSTMASTER)\@/i)
|
567
|
+
false
|
568
|
+
end
|
569
|
+
|
570
|
+
def detect_bounce_status_code_from_text(text)
|
571
|
+
# Parses a text and uses pattern matching to determines its error status (RFC 3463)
|
572
|
+
# from: https://github.com/mailtop/bounce_email
|
573
|
+
return "5.0.0" if text.match(/Status: 5\.0\.0/i)
|
574
|
+
return "5.1.1" if text.match(/no such (address|user)|Recipient address rejected|User unknown|does not like recipient|The recipient was unavailable to take delivery of the message|Sorry, no mailbox here by that name|invalid address|unknown user|unknown local part|user not found|invalid recipient|failed after I sent the message|did not reach the following recipient|nicht zugestellt werden|o pode ser entregue para um ou mais/i)
|
575
|
+
return "5.1.2" if text.match(/unrouteable mail domain|Esta casilla ha expirado por falta de uso|I couldn't find any host named/i)
|
576
|
+
if text.match(/mailbox is full|Mailbox quota (usage|disk) exceeded|quota exceeded|Over quota|User mailbox exceeds allowed size|Message rejected\. Not enough storage space|user has exhausted allowed storage space|too many messages on the server|mailbox is over quota|mailbox exceeds allowed size|excedeu a quota/i)
|
577
|
+
return "5.2.2" if text.match(/This is a permanent error||(Status: |)5\.2\.2/i)
|
578
|
+
return "4.2.2"
|
579
|
+
end
|
580
|
+
return "5.1.0" if text.match(/Address rejected/)
|
581
|
+
return "4.1.2" if text.match(/I couldn't find any host by that name/)
|
582
|
+
return "4.2.0" if text.match(/not yet been delivered/i)
|
583
|
+
return "5.1.1" if text.match(/mailbox unavailable|No such mailbox|RecipientNotFound|not found by SMTP address lookup|Status: 5\.1\.1/i)
|
584
|
+
return "5.2.3" if text.match(/Status: 5\.2\.3/i) # Too messages in folder
|
585
|
+
return "5.4.0" if text.match(/Status: 5\.4\.0/i) # too many hops
|
586
|
+
return "5.4.4" if text.match(/Unrouteable address/i)
|
587
|
+
return "4.4.7" if text.match(/retry timeout exceeded/i)
|
588
|
+
return "5.2.0" if text.match(/The account or domain may not exist, they may be blacklisted, or missing the proper dns entries./i)
|
589
|
+
return "5.5.4" if text.match(/554 TRANSACTION FAILED/i)
|
590
|
+
return "4.4.1" if text.match(/Status: 4.4.1|delivery temporarily suspended|wasn't able to establish an SMTP connection/i)
|
591
|
+
return "5.5.0" if text.match(/550 OU\-002|Mail rejected by Windows Live Hotmail for policy reasons/i)
|
592
|
+
return "5.1.2" if text.match(/PERM_FAILURE: DNS Error: Domain name not found/i)
|
593
|
+
return "4.2.0" if text.match(/Delivery attempts will continue to be made for/i)
|
594
|
+
return "5.5.4" if text.match(/554 delivery error:/i)
|
595
|
+
return "5.1.1" if text.match(/550-5.1.1|This Gmail user does not exist/i)
|
596
|
+
return "5.7.1" if text.match(/5.7.1 Your message.*?was blocked by ROTA DNSBL/i) # AA added
|
597
|
+
return "5.7.2" if text.match(/not have permission to post messages to the group/i)
|
598
|
+
return "5.3.2" if text.match(/Technical details of permanent failure|Too many bad recipients/i) && (text.match(/The recipient server did not accept our requests to connect/i) || text.match(/Connection was dropped by remote host/i) || text.match(/Could not initiate SMTP conversation/i)) # AA added
|
599
|
+
return "4.3.2" if text.match(/Technical details of temporary failure/i) && (text.match(/The recipient server did not accept our requests to connect/i) || text.match(/Connection was dropped by remote host/i) || text.match(/Could not initiate SMTP conversation/i)) # AA added
|
600
|
+
return "5.0.0" if text.match(/Delivery to the following recipient failed permanently/i) # AA added
|
601
|
+
return '5.2.3' if text.match(/account closed|account has been disabled or discontinued|mailbox not found|prohibited by administrator|access denied|account does not exist/i)
|
602
|
+
end
|
529
603
|
end
|
530
604
|
end
|
@@ -10,6 +10,10 @@ module Schleuder
|
|
10
10
|
validates :fingerprint, allow_blank: true, fingerprint: true
|
11
11
|
validates :delivery_enabled, :admin, boolean: true
|
12
12
|
|
13
|
+
before_validation {
|
14
|
+
self.email = Mail::Address.new(self.email).address
|
15
|
+
}
|
16
|
+
|
13
17
|
default_scope { order(:email) }
|
14
18
|
|
15
19
|
scope :without_fingerprint, -> { where(fingerprint: [nil,'']) }
|
@@ -37,10 +41,10 @@ module Schleuder
|
|
37
41
|
list.keys("0x#{self.fingerprint}").first
|
38
42
|
end
|
39
43
|
|
40
|
-
def send_mail(mail)
|
44
|
+
def send_mail(mail, incoming_mail=nil)
|
41
45
|
list.logger.debug "Preparing sending to #{self.inspect}"
|
42
46
|
|
43
|
-
mail = ensure_headers(mail)
|
47
|
+
mail = ensure_headers(mail, incoming_mail)
|
44
48
|
gpg_opts = self.list.gpg_sign_options
|
45
49
|
|
46
50
|
if self.key.blank?
|
@@ -66,9 +70,28 @@ module Schleuder
|
|
66
70
|
mail.deliver
|
67
71
|
end
|
68
72
|
|
69
|
-
def ensure_headers(mail)
|
73
|
+
def ensure_headers(mail, incoming_mail=nil)
|
70
74
|
mail.to = self.email
|
71
|
-
|
75
|
+
|
76
|
+
if self.list.set_reply_to_to_sender? && ! incoming_mail.nil?
|
77
|
+
# If the option "set_reply_to_to_sender" is set to true, we will set the reply-to header
|
78
|
+
# to the reply-to header given by the original email. If no reply-to header exists in the original email,
|
79
|
+
# the original senders email will be used as reply-to.
|
80
|
+
if ! incoming_mail.reply_to.nil?
|
81
|
+
mail.reply_to = incoming_mail.reply_to
|
82
|
+
else
|
83
|
+
mail.reply_to = incoming_mail.from
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
if self.list.munge_from? && ! incoming_mail.nil?
|
88
|
+
# If the option "munge_from" is set to true, we will add the original senders' from-header to ours.
|
89
|
+
# We munge the from-header to avoid issues with DMARC.
|
90
|
+
mail.from = I18n.t("header_munging", from: incoming_mail.from.first, list: self.list.email, list_address: self.list.email)
|
91
|
+
else
|
92
|
+
mail.from = self.list.email
|
93
|
+
end
|
94
|
+
|
72
95
|
mail.sender = self.list.bounce_address
|
73
96
|
mail
|
74
97
|
end
|
data/lib/schleuder/version.rb
CHANGED
data/locales/de.yml
CHANGED
data/locales/en.yml
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schleuder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schleuder dev team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gpgme
|
@@ -291,6 +291,7 @@ files:
|
|
291
291
|
- db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb
|
292
292
|
- db/migrate/20180723173900_add_deliver_selfsent_to_list.rb
|
293
293
|
- db/migrate/20190906194820_add_autocrypt_header_to_list.rb
|
294
|
+
- db/migrate/20200118170110_add_set_reply_to_to_sender_and_munge_from.rb
|
294
295
|
- db/schema.rb
|
295
296
|
- etc/init.d/schleuder-api-daemon
|
296
297
|
- etc/list-defaults.yml
|