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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8aff8f8ecff4958e630158f6fc5811db96d26699ab0946d16b4fc430e2897387
4
- data.tar.gz: daa4d6fbe4941a311dcfc54d5544b1771d809ca2f570f6060473b0f94f4bc577
3
+ metadata.gz: 648f81aa424a3214a9d03fb66e5b3e216c8990e092d1b5b18ab41a1f0a1e5fd3
4
+ data.tar.gz: 62a5a77189860b4228a2168690f3990d02e53ee4dfb6c458a0273b5407e8a2e7
5
5
  SHA512:
6
- metadata.gz: b125a6734601100a1a768b9295848c4d3c1de0fb8336e7a6cf070972c78913f71c2ff52418a8a16cff705a546ca27de29155cae0c1f30736f4f8127bffe5b823
7
- data.tar.gz: 55bd88009950e4dbc52a3f17f3566ee8f4bd7d43751237f19da514d00e8e3d2dc92817c15660b0d826bb8072cb537f3652cf5e93ebeca43fcc424faf33964978
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.5.3.gem) and [the OpenPGP-signature](https://schleuder.org/download/schleuder-3.5.3.gem.sig) and verify:
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.5.3.gem.sig
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.5.3.gem
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.5.3.tar.gz) and [its OpenPGP-signature](https://schleuder.org/download/schleuder-3.5.3.tar.gz.sig).
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: 20190906194820) do
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|
@@ -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
@@ -4,7 +4,13 @@ module Schleuder
4
4
  class Conf
5
5
  include Singleton
6
6
 
7
- EMAIL_REGEXP = /\A.+@[[:alnum:]_.-]+\z/i
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
 
@@ -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
- # Auto-Submitted exists and does not equal 'no' and:
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
- mail.from = self.list.email
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
@@ -1,3 +1,3 @@
1
1
  module Schleuder
2
- VERSION = '3.5.3'
2
+ VERSION = '3.6.0'
3
3
  end
data/locales/de.yml CHANGED
@@ -261,6 +261,7 @@ de:
261
261
  encryption_states:
262
262
  encrypted: "Verschlüsselt"
263
263
  unencrypted: "Unverschlüsselt"
264
+ header_munging: "%{from} über %{list} <%{list_address}>"
264
265
 
265
266
  activerecord:
266
267
  errors:
data/locales/en.yml CHANGED
@@ -265,6 +265,7 @@ en:
265
265
  encryption_states:
266
266
  encrypted: "Encrypted"
267
267
  unencrypted: "Unencrypted"
268
+ header_munging: "%{from} via %{list} <%{list_address}>"
268
269
 
269
270
  activerecord:
270
271
  errors:
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.5.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: 2020-06-13 00:00:00.000000000 Z
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