schleuder 3.4.1 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -9
- data/Rakefile +6 -4
- data/db/migrate/20180723173900_add_deliver_selfsent_to_list.rb +11 -0
- data/db/migrate/20190906194820_add_autocrypt_header_to_list.rb +11 -0
- data/db/migrate/20200118170110_add_set_reply_to_to_sender_and_munge_from.rb +15 -0
- data/db/schema.rb +5 -1
- data/etc/list-defaults.yml +26 -0
- data/lib/schleuder.rb +10 -0
- data/lib/schleuder/cli.rb +1 -0
- data/lib/schleuder/conf.rb +7 -1
- data/lib/schleuder/gpgme/key.rb +4 -0
- data/lib/schleuder/list.rb +39 -4
- data/lib/schleuder/logger_notifications.rb +2 -0
- data/lib/schleuder/mail/message.rb +105 -22
- data/lib/schleuder/plugins/attach_listkey.rb +6 -10
- data/lib/schleuder/plugins/key_management.rb +2 -19
- data/lib/schleuder/plugins/resend.rb +14 -11
- data/lib/schleuder/runner.rb +30 -2
- data/lib/schleuder/subscription.rb +27 -4
- data/lib/schleuder/version.rb +1 -1
- data/locales/de.yml +5 -1
- data/locales/en.yml +5 -1
- metadata +27 -10
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,14 +15,15 @@ Requirements
|
|
15
15
|
* gpgme
|
16
16
|
* sqlite3
|
17
17
|
* openssl
|
18
|
+
* icu
|
18
19
|
|
19
|
-
*If you use Debian
|
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
|
|
21
22
|
*🛈 A note regarding Ubuntu: All Ubuntu versions up to and including 17.10 don't meet the requirements with their packaged versions of gnupg! To run Schleuder on Ubuntu you currently have to install a more recent version of gnupg manually. Only Ubuntu 18.04 ("bionic") provides modern enough versions of Schleuder's requirements.*
|
22
23
|
|
23
|
-
On systems that base on Debian
|
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:
|
@@ -65,7 +66,7 @@ Installing Schleuder
|
|
65
66
|
This creates necessary directories, copies example configs, etc. If you see errors about missing write permissions please follow the advice given.
|
66
67
|
|
67
68
|
|
68
|
-
For further information on setup and configuration please read <https://schleuder.org/docs
|
69
|
+
For further information on setup and configuration please read <https://schleuder.org/schleuder/docs/server-admins.html>.
|
69
70
|
|
70
71
|
|
71
72
|
Command line usage
|
@@ -112,7 +113,7 @@ To execute the test suite run:
|
|
112
113
|
|
113
114
|
bundle exec rspec
|
114
115
|
|
115
|
-
Please note: Some of the specs use 'pgrep'. On systems that base on Debian
|
116
|
+
Please note: Some of the specs use 'pgrep'. On systems that base on Debian 10 ("buster") install it via
|
116
117
|
|
117
118
|
apt-get install procps
|
118
119
|
|
@@ -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
@@ -3,12 +3,14 @@ require_relative "lib/#{project}.rb"
|
|
3
3
|
|
4
4
|
@version = Schleuder::VERSION
|
5
5
|
@tagname = "#{project}-#{@version}"
|
6
|
-
@gpguid = '
|
6
|
+
@gpguid = 'B3D190D5235C74E1907EACFE898F2C91E2E6E1F3'
|
7
7
|
@filename_gem = "#{@tagname}.gem"
|
8
8
|
@filename_tarball = "#{@tagname}.tar.gz"
|
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'
|
@@ -110,7 +112,7 @@ end
|
|
110
112
|
desc 'Publish gem-file to rubygems.org'
|
111
113
|
task :publish_gem do
|
112
114
|
puts "Really push #{@filename_gem} to rubygems.org? [yN]"
|
113
|
-
if gets.match(/^y/i)
|
115
|
+
if $stdin.gets.match(/^y/i)
|
114
116
|
puts "Pushing..."
|
115
117
|
`gem push #{@filename_gem}`
|
116
118
|
else
|
@@ -132,7 +134,7 @@ desc 'Check if version-tag already exists'
|
|
132
134
|
task :check_version do
|
133
135
|
# Check if Schleuder::VERSION has been updated since last release
|
134
136
|
if `git tag`.match?(/^#{@tagname}$/)
|
135
|
-
$stderr.puts "Warning: Tag '#{@tagname}' already exists. Did you forget to update
|
137
|
+
$stderr.puts "Warning: Tag '#{@tagname}' already exists. Did you forget to update lib/#{project}/version.rb?"
|
136
138
|
$stderr.print "Delete tag to continue? [yN] "
|
137
139
|
if $stdin.gets.match(/^y/i)
|
138
140
|
`git tag -d #{@tagname}`
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddDeliverSelfsentToList < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if ! column_exists?(:lists, :deliver_selfsent)
|
4
|
+
add_column :lists, :deliver_selfsent, :boolean, default: true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_column(:lists, :deliver_selfsent)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddAutocryptHeaderToList < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if ! column_exists?(:lists, :include_autocrypt_header)
|
4
|
+
add_column :lists, :include_autocrypt_header, :boolean, default: true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_column(:lists, :include_autocrypt_header)
|
10
|
+
end
|
11
|
+
end
|
@@ -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"
|
@@ -37,6 +37,7 @@ ActiveRecord::Schema.define(version: 20180110203100) do
|
|
37
37
|
t.boolean "keep_msgid", default: true
|
38
38
|
t.boolean "bounces_drop_all", default: false
|
39
39
|
t.boolean "bounces_notify_admins", default: true
|
40
|
+
t.boolean "deliver_selfsent", default: true
|
40
41
|
t.boolean "include_list_headers", default: true
|
41
42
|
t.boolean "include_openpgp_header", default: true
|
42
43
|
t.integer "max_message_size_kb", default: 10240
|
@@ -44,6 +45,9 @@ ActiveRecord::Schema.define(version: 20180110203100) do
|
|
44
45
|
t.boolean "forward_all_incoming_to_admins", default: false
|
45
46
|
t.integer "logfiles_to_keep", default: 2
|
46
47
|
t.text "internal_footer", default: ""
|
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
|
47
51
|
end
|
48
52
|
|
49
53
|
create_table "subscriptions", force: :cascade do |t|
|
data/etc/list-defaults.yml
CHANGED
@@ -93,6 +93,9 @@ bounces_drop_on_headers:
|
|
93
93
|
# Send a notice to the list-admins whenever an email is bounced or dropped?
|
94
94
|
bounces_notify_admins: true
|
95
95
|
|
96
|
+
# Include Autocrypt header into emails?
|
97
|
+
include_autocrypt_header: true
|
98
|
+
|
96
99
|
# Include RFC-compliant List-* Headers into emails?
|
97
100
|
include_list_headers: true
|
98
101
|
|
@@ -123,3 +126,26 @@ language: en
|
|
123
126
|
# Forward a raw copy of all incoming emails to the list-admins?
|
124
127
|
# Mainly useful for debugging.
|
125
128
|
forward_all_incoming_to_admins: false
|
129
|
+
|
130
|
+
# Should e-mails be delivered to the original subscribed sender?
|
131
|
+
# Disabling this only works for signed e-mails; any e-mail that is unsigned
|
132
|
+
# sent to the list is treated as coming from an unknown source
|
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.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# default to UTF-8 encoding as early as possible for external
|
2
|
+
# data.
|
3
|
+
#
|
4
|
+
# this should ensure we are able to parse most incoming
|
5
|
+
# plain text mails in different charsets.
|
6
|
+
Encoding.default_external = Encoding::UTF_8
|
7
|
+
|
1
8
|
# Stdlib
|
2
9
|
require 'fileutils'
|
3
10
|
require 'singleton'
|
@@ -61,6 +68,9 @@ ENV["SCHLEUDER_CONFIG"] ||= '/etc/schleuder/schleuder.yml'
|
|
61
68
|
ENV["SCHLEUDER_LIST_DEFAULTS"] ||= '/etc/schleuder/list-defaults.yml'
|
62
69
|
ENV["SCHLEUDER_ENV"] ||= 'production'
|
63
70
|
ENV["SCHLEUDER_ROOT"] = rootdir.to_s
|
71
|
+
# Ensure that gnupg never-ever tries to ask for a passphrase.
|
72
|
+
ENV["GPG_TTY"] = "/nonexistant-#{rand}"
|
73
|
+
ENV["DISPLAY"] = nil
|
64
74
|
|
65
75
|
GPGME::Ctx.set_gpg_path_from_env
|
66
76
|
GPGME::Ctx.check_gpg_version
|
data/lib/schleuder/cli.rb
CHANGED
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/gpgme/key.rb
CHANGED
@@ -57,6 +57,10 @@ module GPGME
|
|
57
57
|
"#{self.to_s}\n\n#{export(armor: true).read}"
|
58
58
|
end
|
59
59
|
|
60
|
+
def minimal
|
61
|
+
export(minimal: true).to_s
|
62
|
+
end
|
63
|
+
|
60
64
|
# Force encoding, some databases save "ASCII-8BIT" as binary data.
|
61
65
|
alias_method :orig_fingerprint, :fingerprint
|
62
66
|
def fingerprint
|
data/lib/schleuder/list.rb
CHANGED
@@ -19,6 +19,7 @@ module Schleuder
|
|
19
19
|
:receive_admin_only,
|
20
20
|
:keep_msgid,
|
21
21
|
:bounces_drop_all,
|
22
|
+
:deliver_selfsent,
|
22
23
|
:bounces_notify_admins,
|
23
24
|
:include_list_headers,
|
24
25
|
:include_openpgp_header,
|
@@ -66,6 +67,22 @@ module Schleuder
|
|
66
67
|
with: /\A[[:graph:]\s]*\z/i,
|
67
68
|
}
|
68
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
|
+
|
69
86
|
default_scope { order(:email) }
|
70
87
|
|
71
88
|
def self.configurable_attributes
|
@@ -146,6 +163,16 @@ module Schleuder
|
|
146
163
|
key.armored
|
147
164
|
end
|
148
165
|
|
166
|
+
def key_minimal_base64_encoded(fingerprint=self.fingerprint)
|
167
|
+
key = keys(fingerprint).first
|
168
|
+
|
169
|
+
if key.blank?
|
170
|
+
return false
|
171
|
+
end
|
172
|
+
|
173
|
+
Base64.strict_encode64(key.minimal)
|
174
|
+
end
|
175
|
+
|
149
176
|
def check_keys
|
150
177
|
now = Time.now
|
151
178
|
checkdate = now + (60 * 60 * 24 * 14) # two weeks
|
@@ -340,16 +367,24 @@ module Schleuder
|
|
340
367
|
true
|
341
368
|
end
|
342
369
|
|
343
|
-
def send_to_subscriptions(mail)
|
370
|
+
def send_to_subscriptions(mail, incoming_mail=nil)
|
344
371
|
logger.debug "Sending to subscriptions."
|
345
372
|
mail.add_internal_footer!
|
346
373
|
self.subscriptions.each do |subscription|
|
347
374
|
begin
|
348
|
-
|
349
|
-
|
350
|
-
else
|
375
|
+
|
376
|
+
if ! subscription.delivery_enabled
|
351
377
|
logger.info "Not sending to #{subscription.email}: delivery is disabled."
|
378
|
+
next
|
379
|
+
end
|
380
|
+
|
381
|
+
if ! self.deliver_selfsent && incoming_mail.was_validly_signed? && ( subscription == incoming_mail.signer )
|
382
|
+
logger.info "Not sending to #{subscription.email}: delivery of self sent is disabled."
|
383
|
+
next
|
352
384
|
end
|
385
|
+
|
386
|
+
subscription.send_mail(mail, incoming_mail)
|
387
|
+
|
353
388
|
rescue => exc
|
354
389
|
msg = I18n.t('errors.delivery_error',
|
355
390
|
{ email: subscription.email, error: exc.to_s })
|
@@ -17,6 +17,7 @@ module Mail
|
|
17
17
|
attr_accessor :original_message
|
18
18
|
attr_accessor :list
|
19
19
|
attr_accessor :protected_headers_subject
|
20
|
+
attr_writer :dynamic_pseudoheaders
|
20
21
|
|
21
22
|
# TODO: This should be in initialize(), but I couldn't understand the
|
22
23
|
# strange errors about wrong number of arguments when overriding
|
@@ -44,9 +45,7 @@ module Mail
|
|
44
45
|
# might be gone (e.g. request-keywords that delete subscriptions or
|
45
46
|
# keys).
|
46
47
|
new.signer
|
47
|
-
self.dynamic_pseudoheaders.
|
48
|
-
new.add_pseudoheader(str)
|
49
|
-
end
|
48
|
+
new.dynamic_pseudoheaders = self.dynamic_pseudoheaders.dup
|
50
49
|
|
51
50
|
# Store previously protected subject for later access.
|
52
51
|
# mail-gpg pulls headers from the decrypted mime parts "up" into the main
|
@@ -206,13 +205,17 @@ module Mail
|
|
206
205
|
@recipient.match(/-bounce@/).present? ||
|
207
206
|
# Empty Return-Path
|
208
207
|
self.return_path.to_s == '<>' ||
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
208
|
+
bounced?
|
209
|
+
end
|
210
|
+
|
211
|
+
def bounced?
|
212
|
+
@bounced ||= bounce_detected? || (error_status != "unknown")
|
214
213
|
end
|
215
214
|
|
215
|
+
def error_status
|
216
|
+
@error_status ||= detect_error_code
|
217
|
+
end
|
218
|
+
|
216
219
|
def keywords
|
217
220
|
return @keywords if @keywords
|
218
221
|
|
@@ -244,11 +247,11 @@ module Mail
|
|
244
247
|
# decide itself how to encode, it works. If we don't, some
|
245
248
|
# character-sequences are not properly re-encoded.
|
246
249
|
part.content_transfer_encoding = nil
|
247
|
-
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
part.body =
|
250
|
+
|
251
|
+
# Set the right charset on the now parsed body
|
252
|
+
new_body = lines.compact.join
|
253
|
+
part.charset = new_body.encoding.to_s
|
254
|
+
part.body = new_body
|
252
255
|
|
253
256
|
@keywords
|
254
257
|
end
|
@@ -266,20 +269,17 @@ module Mail
|
|
266
269
|
end
|
267
270
|
|
268
271
|
def add_pseudoheader(string_or_key, value=nil)
|
269
|
-
|
270
|
-
if value.present?
|
271
|
-
@dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
|
272
|
-
else
|
273
|
-
@dynamic_pseudoheaders << string_or_key.to_s
|
274
|
-
end
|
272
|
+
dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
|
275
273
|
end
|
276
274
|
|
277
275
|
def make_pseudoheader(key, value)
|
278
|
-
"#{key.to_s.camelize}: #{value.to_s}"
|
276
|
+
output = "#{key.to_s.camelize}: #{value.to_s}"
|
277
|
+
# wrap lines after 76 with 2 indents
|
278
|
+
output.gsub(/(.{1,76})( +|$)\n?/, " \\1\n").chomp.lstrip
|
279
279
|
end
|
280
280
|
|
281
281
|
def dynamic_pseudoheaders
|
282
|
-
@dynamic_pseudoheaders
|
282
|
+
@dynamic_pseudoheaders ||= []
|
283
283
|
end
|
284
284
|
|
285
285
|
def signature_state
|
@@ -330,7 +330,8 @@ module Mail
|
|
330
330
|
end
|
331
331
|
|
332
332
|
def pseudoheaders(list)
|
333
|
-
|
333
|
+
separator = '------------------------------------------------------------------------------'
|
334
|
+
(standard_pseudoheaders(list) + dynamic_pseudoheaders).flatten.join("\n") + "\n" + separator + "\n"
|
334
335
|
end
|
335
336
|
|
336
337
|
def add_msgids(list, orig)
|
@@ -345,6 +346,14 @@ module Mail
|
|
345
346
|
end
|
346
347
|
|
347
348
|
def add_list_headers(list)
|
349
|
+
if list.include_autocrypt_header
|
350
|
+
# Inject whitespaces, to let Mail break the string at these points
|
351
|
+
# leading to correct wrapping.
|
352
|
+
keydata = list.key_minimal_base64_encoded.gsub(/(.{78})/, '\1 ')
|
353
|
+
|
354
|
+
self['Autocrypt'] = "addr=#{list.email}; prefer-encrypt=mutual; keydata=#{keydata}"
|
355
|
+
end
|
356
|
+
|
348
357
|
if list.include_list_headers
|
349
358
|
self['List-Id'] = "<#{list.email.gsub('@', '.')}>"
|
350
359
|
self['List-Owner'] = "<mailto:#{list.owner_address}> (Use list's public key)"
|
@@ -517,5 +526,79 @@ module Mail
|
|
517
526
|
end
|
518
527
|
end.join(' ')
|
519
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
|
520
603
|
end
|
521
604
|
end
|
@@ -1,16 +1,12 @@
|
|
1
1
|
module Schleuder
|
2
2
|
module ListPlugins
|
3
3
|
def self.attach_listkey(arguments, list, mail)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
})
|
11
|
-
mail.attachments[filename].content_type = 'application/pgp-keys'
|
12
|
-
mail.attachments[filename].content_description = "OpenPGP public key of #{list.email}"
|
13
|
-
mail.attachments[filename].content_disposition = "attachment; filename=#{filename}"
|
4
|
+
new_part = Mail::Part.new
|
5
|
+
new_part.body = list.export_key
|
6
|
+
new_part.content_type = 'application/pgp-keys'
|
7
|
+
new_part.content_description = "OpenPGP public key of #{list.email}"
|
8
|
+
new_part.content_disposition = "attachment; filename=#{list.fingerprint}.pgpkey"
|
9
|
+
mail.add_part new_part
|
14
10
|
nil
|
15
11
|
end
|
16
12
|
end
|
@@ -104,35 +104,18 @@ module Schleuder
|
|
104
104
|
# helper methods
|
105
105
|
private
|
106
106
|
|
107
|
-
def self.is_armored_key?(material)
|
108
|
-
return false unless /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/ =~ material
|
109
|
-
return false unless /^-----END PGP PUBLIC KEY BLOCK-----$/ =~ material
|
110
|
-
|
111
|
-
lines = material.split("\n").reject(&:empty?)
|
112
|
-
# remove header
|
113
|
-
lines.shift
|
114
|
-
# remove tail
|
115
|
-
lines.pop
|
116
|
-
# verify the rest
|
117
|
-
# TODO: verify length except for lasts lines?
|
118
|
-
# headers according to https://tools.ietf.org/html/rfc4880#section-6.2
|
119
|
-
lines.map do |line|
|
120
|
-
/\A((comment|version|messageid|hash|charset):.*|[0-9a-z\/=+]+)\Z/i =~ line
|
121
|
-
end.all?
|
122
|
-
end
|
123
|
-
|
124
107
|
def self.import_keys_from_attachments(list, mail)
|
125
108
|
mail.attachments.map do |attachment|
|
126
109
|
material = attachment.body.to_s
|
127
110
|
|
128
|
-
list.import_key(material)
|
111
|
+
list.import_key(material)
|
129
112
|
end
|
130
113
|
end
|
131
114
|
|
132
115
|
def self.import_key_from_body(list, mail)
|
133
116
|
key_material = mail.first_plaintext_part.body.to_s
|
134
117
|
|
135
|
-
list.import_key(key_material)
|
118
|
+
list.import_key(key_material)
|
136
119
|
end
|
137
120
|
end
|
138
121
|
end
|
@@ -56,6 +56,9 @@ module Schleuder
|
|
56
56
|
|
57
57
|
# Only continue if all recipients are still here.
|
58
58
|
if recip_map.size < arguments.size
|
59
|
+
recip_map.keys.each do |aborted_sender|
|
60
|
+
mail.add_pseudoheader(:error, I18n.t("plugins.resend.aborted", email: aborted_sender))
|
61
|
+
end
|
59
62
|
return
|
60
63
|
end
|
61
64
|
|
@@ -117,22 +120,22 @@ module Schleuder
|
|
117
120
|
Array(recipients).inject({}) do |hash, email|
|
118
121
|
keys = mail.list.keys(email)
|
119
122
|
# Exclude unusable keys.
|
120
|
-
keys.select
|
121
|
-
case
|
123
|
+
usable_keys = keys.select { |key| key.usable_for?(:encrypt) }
|
124
|
+
case usable_keys.size
|
122
125
|
when 1
|
123
|
-
hash[email] =
|
126
|
+
hash[email] = usable_keys.first
|
124
127
|
when 0
|
125
128
|
if encrypted_only
|
126
129
|
# Don't add the email to the result to exclude it from the
|
127
130
|
# recipients.
|
128
|
-
|
131
|
+
add_resend_msg(mail, email, :error, 'not_resent_no_keys', usable_keys.size, keys.size)
|
129
132
|
else
|
130
133
|
hash[email] = ''
|
131
134
|
end
|
132
135
|
else
|
133
136
|
# Always report this situation, regardless of sending or not. It's
|
134
137
|
# bad and should be fixed.
|
135
|
-
|
138
|
+
add_resend_msg(mail, email, :notice, 'not_resent_encrypted_no_keys', usable_keys.size, keys.size)
|
136
139
|
if ! encrypted_only
|
137
140
|
hash[email] = ''
|
138
141
|
end
|
@@ -152,8 +155,8 @@ module Schleuder
|
|
152
155
|
gpg_opts
|
153
156
|
end
|
154
157
|
|
155
|
-
def self.
|
156
|
-
mail.add_pseudoheader(
|
158
|
+
def self.add_resend_msg(mail, email, severity, msg, usable_keys_size, all_keys_size)
|
159
|
+
mail.add_pseudoheader(severity, I18n.t("plugins.resend.#{msg}", email: email, usable_keys: usable_keys_size, all_keys: all_keys_size))
|
157
160
|
end
|
158
161
|
|
159
162
|
def self.add_error_header(mail, recipients_map)
|
@@ -163,15 +166,15 @@ module Schleuder
|
|
163
166
|
def self.add_resent_headers(mail, recipients_map, to_or_cc, sent_encrypted)
|
164
167
|
if sent_encrypted
|
165
168
|
prefix = I18n.t('plugins.resend.encrypted_to')
|
166
|
-
str = recipients_map.map do |email, key|
|
169
|
+
str = "\n" + recipients_map.map do |email, key|
|
167
170
|
"#{email} (#{key.fingerprint})"
|
168
|
-
end.join(
|
171
|
+
end.join(",\n")
|
169
172
|
else
|
170
173
|
prefix = I18n.t('plugins.resend.unencrypted_to')
|
171
|
-
str = recipients_map.keys.join(
|
174
|
+
str = ' ' + recipients_map.keys.join(", ")
|
172
175
|
end
|
173
176
|
headername = resent_header_name(to_or_cc)
|
174
|
-
mail.add_pseudoheader(headername, "#{prefix}
|
177
|
+
mail.add_pseudoheader(headername, "#{prefix}#{str}")
|
175
178
|
end
|
176
179
|
|
177
180
|
def self.resent_header_name(to_or_cc)
|
data/lib/schleuder/runner.rb
CHANGED
@@ -5,15 +5,43 @@ module Schleuder
|
|
5
5
|
return error if error
|
6
6
|
|
7
7
|
logger.info "Parsing incoming email."
|
8
|
+
|
9
|
+
# is it valid utf-8?
|
10
|
+
msg_scrubbed = false
|
11
|
+
unless msg.valid_encoding?
|
12
|
+
logger.warn "Converting message due to invalid characters"
|
13
|
+
detection = CharlockHolmes::EncodingDetector.detect(msg)
|
14
|
+
begin
|
15
|
+
msg = CharlockHolmes::Converter.convert(msg, detection[:encoding], 'UTF-8')
|
16
|
+
rescue ArgumentError
|
17
|
+
# it looks like even icu wasn't able to convert
|
18
|
+
# so we scrub the invalid characters to be able to
|
19
|
+
# at least parse the message somehow. Though this might
|
20
|
+
# result in data loss.
|
21
|
+
logger.warn "Scrubbing message due to invalid characters"
|
22
|
+
msg = msg.scrub
|
23
|
+
msg_scrubbed = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
8
27
|
@mail = Mail.create_message_to_list(msg, recipient, list)
|
9
28
|
|
29
|
+
if msg_scrubbed
|
30
|
+
@mail.add_pseudoheader(:note, I18n.t("pseudoheaders.scrubbed_message"))
|
31
|
+
end
|
32
|
+
|
10
33
|
error = run_filters('pre')
|
11
34
|
return error if error
|
12
35
|
|
13
36
|
begin
|
14
37
|
# This decrypts, verifies, etc.
|
15
38
|
@mail = @mail.setup
|
16
|
-
|
39
|
+
|
40
|
+
rescue GPGME::Error::BadPassphrase,
|
41
|
+
GPGME::Error::DecryptFailed,
|
42
|
+
GPGME::Error::NoData,
|
43
|
+
GPGME::Error::NoSecretKey
|
44
|
+
|
17
45
|
logger.warn "Decryption of incoming message failed."
|
18
46
|
return Errors::DecryptionFailed.new(list)
|
19
47
|
end
|
@@ -46,7 +74,7 @@ module Schleuder
|
|
46
74
|
# Subscriptions
|
47
75
|
logger.debug "Creating clean copy of message"
|
48
76
|
copy = @mail.clean_copy(list.headers_to_meta.any?)
|
49
|
-
list.send_to_subscriptions(copy)
|
77
|
+
list.send_to_subscriptions(copy, @mail)
|
50
78
|
nil
|
51
79
|
end
|
52
80
|
|
@@ -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
@@ -121,7 +121,9 @@ de:
|
|
121
121
|
Oder, um einen Schlüssel per HTTP von einem Server zu laden:
|
122
122
|
X-FETCH-KEY: https://example.org/keys/mykey.asc
|
123
123
|
resend:
|
124
|
-
not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{
|
124
|
+
not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{all_keys} Schlüssel gefunden, davon %{usable_keys} nutzbar. Unverschlüsseltes Senden verboten).
|
125
|
+
not_resent_encrypted_no_keys: Verschlüsseltes Resending an <%{email}> fehlgeschlagen (%{all_keys} Schlüssel gefunden, davon %{usable_keys} nutzbar).
|
126
|
+
aborted: Resending an <%{email}> abgebrochen aufgrund anderer Probleme.
|
125
127
|
encrypted_to: Verschlüsselt an
|
126
128
|
unencrypted_to: Unverschlüsselt an
|
127
129
|
invalid_recipient: "Ungültige Emailadresse für resend: %{address}"
|
@@ -250,6 +252,7 @@ de:
|
|
250
252
|
fetch_key:
|
251
253
|
invalid_input: "Ungültige Angabe. Gültig sind: URLs, OpenPGP-Fingerabdrücke, oder Emailadressen."
|
252
254
|
pseudoheaders:
|
255
|
+
scrubbed_message: Diese Email enthielt ungültige Zeichen, die aus Verarbeitungsgründen möglicherweise entfernt wurden.
|
253
256
|
stripped_html_from_multialt: Diese Email enthielt einen alternativen HTML-Teil, der PGP-Daten beinhaltete. Der HTML-Teil wurde entfernt, um die Email sauberer analysieren zu können.
|
254
257
|
stripped_html_from_multialt_with_keywords: Diese Email enthielt Schlüsselwörter und einen alternativen HTML-Teil. Der HTML-Teil wurde entfernt, um zu verhindern dass diese Schlüsselwörter Aussenstehenden bekannt werden.
|
255
258
|
signature_states:
|
@@ -258,6 +261,7 @@ de:
|
|
258
261
|
encryption_states:
|
259
262
|
encrypted: "Verschlüsselt"
|
260
263
|
unencrypted: "Unverschlüsselt"
|
264
|
+
header_munging: "%{from} über %{list} <%{list_address}>"
|
261
265
|
|
262
266
|
activerecord:
|
263
267
|
errors:
|
data/locales/en.yml
CHANGED
@@ -125,7 +125,9 @@ en:
|
|
125
125
|
Or, to fetch a key keys by URL:
|
126
126
|
X-FETCH-KEY: https://example.org/keys/mykey.asc
|
127
127
|
resend:
|
128
|
-
not_resent_no_keys: Resending to <%{email}> failed (%{
|
128
|
+
not_resent_no_keys: Resending to <%{email}> failed (%{all_keys} keys found, of which %{usable_keys} can be used. Unencrypted sending not allowed).
|
129
|
+
not_resent_encrypted_no_keys: Resending as encrypted email to <%{email}> failed (%{all_keys} keys found, of which %{usable_keys} can be used).
|
130
|
+
aborted: Resending to <%{email}> aborted due to other errors.
|
129
131
|
encrypted_to: Encrypted to
|
130
132
|
unencrypted_to: Unencrypted to
|
131
133
|
invalid_recipient: "Invalid email-address for resending: %{address}"
|
@@ -254,6 +256,7 @@ en:
|
|
254
256
|
fetch_key:
|
255
257
|
invalid_input: "Invalid input. Allowed are: URLs, OpenPGP-fingerprints, or email-addresses."
|
256
258
|
pseudoheaders:
|
259
|
+
scrubbed_message: This message included invalid characters, which might have been removed to be able to process the message properly.
|
257
260
|
stripped_html_from_multialt: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.
|
258
261
|
stripped_html_from_multialt_with_keywords: This message included keywords and an alternating HTML-part. The HTML-part was removed to prevent the disclosure of these keywords to third parties.
|
259
262
|
signature_states:
|
@@ -262,6 +265,7 @@ en:
|
|
262
265
|
encryption_states:
|
263
266
|
encrypted: "Encrypted"
|
264
267
|
unencrypted: "Unencrypted"
|
268
|
+
header_munging: "%{from} via %{list} <%{list_address}>"
|
265
269
|
|
266
270
|
activerecord:
|
267
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.
|
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
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '2.0'
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 2.0.
|
22
|
+
version: 2.0.19
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: '2.0'
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 2.0.
|
32
|
+
version: 2.0.19
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: mail
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,9 +51,9 @@ dependencies:
|
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0.3'
|
54
|
-
- - "
|
54
|
+
- - "<"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 0.
|
56
|
+
version: 0.4.3
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -61,9 +61,9 @@ dependencies:
|
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
63
|
version: '0.3'
|
64
|
-
- - "
|
64
|
+
- - "<"
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: 0.
|
66
|
+
version: 0.4.3
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: activerecord
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,6 +176,20 @@ dependencies:
|
|
176
176
|
- - "~>"
|
177
177
|
- !ruby/object:Gem::Version
|
178
178
|
version: '1'
|
179
|
+
- !ruby/object:Gem::Dependency
|
180
|
+
name: charlock_holmes
|
181
|
+
requirement: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - "~>"
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: 0.7.6
|
186
|
+
type: :runtime
|
187
|
+
prerelease: false
|
188
|
+
version_requirements: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - "~>"
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: 0.7.6
|
179
193
|
- !ruby/object:Gem::Dependency
|
180
194
|
name: rspec
|
181
195
|
requirement: !ruby/object:Gem::Requirement
|
@@ -249,7 +263,7 @@ dependencies:
|
|
249
263
|
description: |-
|
250
264
|
Schleuder is a group's email-gateway: subscribers can exchange encrypted emails among themselves, receive emails from non-subscribers and send emails to non-subscribers via the list.
|
251
265
|
|
252
|
-
(Please note: For some platforms there's a better way of installing Schleuder than `gem install`. See <https://schleuder.org/docs
|
266
|
+
(Please note: For some platforms there's a better way of installing Schleuder than `gem install`. See <https://schleuder.org/schleuder/docs/server-admins.html#installation> for details.)
|
253
267
|
email: team@schleuder.org
|
254
268
|
executables:
|
255
269
|
- schleuder
|
@@ -275,6 +289,9 @@ files:
|
|
275
289
|
- db/migrate/20160501172700_fix_headers_to_meta_defaults.rb
|
276
290
|
- db/migrate/20170713215059_add_internal_footer_to_list.rb
|
277
291
|
- db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb
|
292
|
+
- db/migrate/20180723173900_add_deliver_selfsent_to_list.rb
|
293
|
+
- db/migrate/20190906194820_add_autocrypt_header_to_list.rb
|
294
|
+
- db/migrate/20200118170110_add_set_reply_to_to_sender_and_munge_from.rb
|
278
295
|
- db/schema.rb
|
279
296
|
- etc/init.d/schleuder-api-daemon
|
280
297
|
- etc/list-defaults.yml
|
@@ -391,7 +408,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
391
408
|
- !ruby/object:Gem::Version
|
392
409
|
version: '0'
|
393
410
|
requirements: []
|
394
|
-
rubyforge_project:
|
411
|
+
rubyforge_project:
|
395
412
|
rubygems_version: 2.7.6.2
|
396
413
|
signing_key:
|
397
414
|
specification_version: 4
|