schleuder 3.5.3 → 3.6.0

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
  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