schleuder 2.2.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.
Files changed (45) hide show
  1. data.tar.gz.sig +0 -0
  2. data/LICENSE +339 -0
  3. data/README +32 -0
  4. data/bin/schleuder +96 -0
  5. data/bin/schleuder-fix-gem-dependencies +30 -0
  6. data/bin/schleuder-init-setup +37 -0
  7. data/bin/schleuder-migrate-v2.1-to-v2.2 +205 -0
  8. data/bin/schleuder-newlist +384 -0
  9. data/contrib/check-expired-keys.rb +59 -0
  10. data/contrib/mutt-schleuder-colors.rc +10 -0
  11. data/contrib/mutt-schleuder-resend.vim +24 -0
  12. data/contrib/smtpserver.rb +76 -0
  13. data/ext/default-list.conf +146 -0
  14. data/ext/default-members.conf +7 -0
  15. data/ext/list.conf.example +14 -0
  16. data/ext/schleuder.conf +62 -0
  17. data/lib/schleuder.rb +49 -0
  18. data/lib/schleuder/archiver.rb +46 -0
  19. data/lib/schleuder/crypt.rb +188 -0
  20. data/lib/schleuder/errors.rb +5 -0
  21. data/lib/schleuder/list.rb +177 -0
  22. data/lib/schleuder/list_config.rb +146 -0
  23. data/lib/schleuder/log/listlogger.rb +56 -0
  24. data/lib/schleuder/log/outputter/emailoutputter.rb +118 -0
  25. data/lib/schleuder/log/outputter/metaemailoutputter.rb +50 -0
  26. data/lib/schleuder/log/schleuderlogger.rb +23 -0
  27. data/lib/schleuder/mail.rb +861 -0
  28. data/lib/schleuder/mailer.rb +26 -0
  29. data/lib/schleuder/member.rb +69 -0
  30. data/lib/schleuder/plugin.rb +54 -0
  31. data/lib/schleuder/processor.rb +363 -0
  32. data/lib/schleuder/schleuder_config.rb +72 -0
  33. data/lib/schleuder/storage.rb +84 -0
  34. data/lib/schleuder/utils.rb +80 -0
  35. data/lib/schleuder/version.rb +3 -0
  36. data/man/schleuder-newlist.8 +191 -0
  37. data/man/schleuder.8 +400 -0
  38. data/plugins/README +20 -0
  39. data/plugins/manage_keys_plugin.rb +113 -0
  40. data/plugins/manage_members_plugin.rb +152 -0
  41. data/plugins/manage_self_plugin.rb +26 -0
  42. data/plugins/resend_plugin.rb +35 -0
  43. data/plugins/version_plugin.rb +12 -0
  44. metadata +178 -0
  45. metadata.gz.sig +2 -0
@@ -0,0 +1,56 @@
1
+ module Schleuder
2
+ class ListLogger < Log4r::Logger
3
+ # Instantiates the list-logger and sets outputters and level.
4
+ # Warning: Do *not* rely on the list-object here! It's not yet available
5
+ # (this actually is part of setting it up) and you'd produce loops and
6
+ # break the whole thing.
7
+ def initialize(listname, listdir, config)
8
+ # Initialize self
9
+ super('list')
10
+
11
+ # define the initial log_level for all outputters (they inherit it)
12
+ @level = eval("Log4r::#{config.log_level.upcase}")
13
+
14
+ # Setting up outputters.
15
+ fmtr = Log4r::PatternFormatter.new(:pattern => "%d #{listname} %l\t%M")
16
+
17
+ if config.log_file
18
+ require 'log4r/outputter/fileoutputter'
19
+ filename = config.log_file
20
+ filename = File.join(listdir, filename) unless filename[0..0].eql?('/')
21
+ add Log4r::FileOutputter.new("file",
22
+ { :level => @level,
23
+ :filename => filename,
24
+ :formatter => fmtr }
25
+ )
26
+ end
27
+
28
+ if config.log_syslog
29
+ require 'log4r/outputter/syslogoutputter'
30
+ syslogfmtr = Log4r::PatternFormatter.new(:pattern => "#{listname}\t%M")
31
+ add Log4r::SyslogOutputter.new("syslog",
32
+ { :level => @level,
33
+ :ident => 'Schleuder',
34
+ :facility => "LOG_MAIL",
35
+ :formatter => syslogfmtr }
36
+ )
37
+ end
38
+
39
+ if config.log_io
40
+ require 'log4r/outputter/iooutputter'
41
+ require 'stringio'
42
+ io = IO.popen(config.log_io, 'w')
43
+ add Log4r::IOOutputter.new("io", io, :level => @level)
44
+ end
45
+
46
+ # Add this as last outputter, else you'll see logging-statements from
47
+ # sending the email before the original error is logged — that's a little
48
+ # confusing.
49
+ add EmailOutputter.new("email", :formatter => fmtr)
50
+ end
51
+
52
+ def notify_admin(*args)
53
+ Log4r::Outputter['email'].notify_admin *args
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,118 @@
1
+ module Schleuder
2
+ class EmailOutputter < Log4r::EmailOutputter
3
+ def initialize(name, hash={})
4
+ # The class needs to/from/subject, we don't use it
5
+ hash = {:to => Schleuder.config.superadminaddr,
6
+ :from => Schleuder.config.myaddr,
7
+ :'error-to' => Schleuder.config.superadminaddr,
8
+ :subject => 'Error',
9
+ :immediate_at => 'ERROR, FATAL',
10
+ :formatter => formatter,
11
+ :domain => 'schleuder', # necessary for log4r from debian "stable"
12
+ :buffsize => 1024**1024 # set the buff size very high otherwise we would trigger random log mails on random log statements if the buffer is full.
13
+ }.merge(hash)
14
+ @altmsg = "Hello,\n\nsending an encrypted error message to you failed. Therefore you receive only\nthis message and are kindly requested to take care of the encryption problem\n(e.g. fix your keys) and have a look at the logs to find the error.\n\nYours, Schleuder\n"
15
+ super name, hash
16
+ end
17
+
18
+ def format(events)
19
+ events.map { |event| @formatter.format(event) }
20
+ end
21
+
22
+ def notify_admin(subject, msg)
23
+ send_mail(makeemail(subject, msg))
24
+ end
25
+
26
+ def makeemail(subject=@subject, msg=nil)
27
+ m = Mail.new
28
+ m.subject = subject
29
+ m.date = Time.now
30
+ m.from = @from
31
+
32
+ if msg
33
+ case msg
34
+ when String
35
+ m.body = msg
36
+ when Array
37
+ m.body = ''
38
+ m.mime_version = 1.0
39
+ msg.each do |msgpart|
40
+ part = Mail.new
41
+ case msgpart
42
+ when Mail
43
+ # This contruction is needed to have valid boundaries. Did I mention that Tmail sucks?
44
+ part.body = Mail.parse(msgpart.to_s).to_s
45
+ part.content_type = 'message/rfc822'
46
+ when String
47
+ part.body = msgpart.to_s
48
+ part.content_type = 'text/plain'
49
+ else
50
+ # This shouldn't happen (we should only have forwarded emails or
51
+ # strings to notify admins), but who knows.
52
+ part.body = msgpart
53
+ part.content_type = 'application/octet-stream'
54
+ end
55
+ m.parts.push part
56
+ end
57
+ end
58
+ else
59
+ # Get the triggering error-msg(s).
60
+ msg = format(@buff.select { |e| e.level > Log4r::WARN }).join
61
+ # Get the whole log.
62
+ backlog = format(@buff).join
63
+
64
+ infopart = Mail.new
65
+ infopart.body = "An error occurred working for list #{Schleuder.list.listname}:\n\n#{msg}\n\nSee also attachments.\n\n"
66
+ m.parts.push infopart
67
+
68
+ if Schleuder.origmsg
69
+ origm = Mail.new
70
+ origm.body = Schleuder.origmsg << "\n"
71
+ origm.content_type = "message/rfc822"
72
+ origm.set_content_disposition 'inline', { :filename => 'schleuder-orig-message.txt' }
73
+ origm['content-description'] = "'The originally incoming message'"
74
+ m.parts.push origm
75
+ end
76
+
77
+ backlogpart = Mail.new
78
+ backlogpart.body = backlog << "\n\n"
79
+ backlogpart['content-description'] = "'Schleuder logging output'"
80
+ backlogpart.set_content_disposition 'inline', { :filename => 'schleuder-log.txt' }
81
+ m.parts.push backlogpart
82
+ end
83
+
84
+ # Trigger re-parsing of the body, else TMail doesn't know it's multipart... :/
85
+ m.to_s
86
+ m
87
+ end
88
+
89
+ def send_mail(mail=makeemail)
90
+ if @send_mail_lock
91
+ self.level = Log4r::OFF # it's possible that the loglevel haven't yet been changed
92
+ Schleuder.log.warn "This is a loop in sending a mail in the EmailOutputer, breaking!"
93
+ return false
94
+ end
95
+ @send_mail_lock = true
96
+ Schleuder.log.info 'Sending notification to admin'
97
+ Schleuder.list.config.admins.each do |admin|
98
+ Schleuder.log.debug { "Looping for admin #{admin}" }
99
+ m = mail.individualize(admin)
100
+ m.to = admin.email
101
+ sender = Schleuder.config.superadminaddr
102
+ if !Processor.send(m, admin, true, sender)
103
+ m.body = @altmsg
104
+ m.content_type = 'text/plain'
105
+ Processor.send(m, admin, false, sender)
106
+ end
107
+ end
108
+ Schleuder.log.info { 'Sending notification done' }
109
+ rescue => e
110
+ # switch off logging per email, else we create loops here!
111
+ self.level = Log4r::OFF
112
+ raise
113
+ ensure
114
+ @send_mail_lock = false
115
+ @buff.clear
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,50 @@
1
+ module Schleuder
2
+ class MetaEmailOutputter < EmailOutputter
3
+ # TODO: refactor (merge?) with EmailOutputter
4
+ def send_mail
5
+ if @send_mail_lock
6
+ self.level = Log4r::OFF # it's possible that the loglevel haven't yet been changed
7
+ Schleuder.log.warn 'This is a loop in sending a mail in the MetaEmailOutputter, breaking!'
8
+ return false
9
+ end
10
+ @send_mail_lock = true
11
+ Schleuder.log.info { 'notifying superadmin' }
12
+ begin
13
+ mail = makeemail
14
+ mail.to = Schleuder.config.superadminaddr
15
+ r = Member.new('email' => Schleuder.config.superadminaddr)
16
+ m = mail.individualize(r)
17
+ if ! Processor.send(m, r, true, Schleuder.config.superadminaddr).first
18
+ m.body = @altmsg
19
+ m.content_type = 'text/plain'
20
+ Processor.send(m, r, false, Schleuder.config.superadminaddr)
21
+ end
22
+ rescue => e
23
+ Schleuder.log.warn "An error occurred while reporting an error: #{e.message}\n#{e.backtrace[0..3].join("\n")}"
24
+ Schleuder.log.warn "Sending emergency notice to superadmin"
25
+ mail = "From: root@localhost
26
+ To: #{Schleuder.config.superadminaddr}
27
+ Date: #{Time.now.strftime('%a, %d %b %Y %H:%M:%S %z')}
28
+ Subject: Error
29
+
30
+ Serious errors happened working for list #{Schleuder.listname}.
31
+ Please check the logs!
32
+ "
33
+ Mailer.send mail, Schleuder.config.superadminaddr, 'root@localhost', true
34
+ end
35
+ Schleuder.log.info { 'Notifying done' }
36
+ rescue => e
37
+ # switch off logging per email, else we create loops here!
38
+ self.level = Log4r::OFF
39
+ if e.message.frozen?
40
+ Schleuder.log.warn "A problematic error happened. I'll better dump the original message to preserve it:\n\n#{Schleuder.origmsg}"
41
+ else
42
+ e.message << "\nThis is very problematic, I'll better dump the original message to preserve it:\n\n#{Schleuder.origmsg}"
43
+ end
44
+ Schleuder.log.fatal { e }
45
+ ensure
46
+ @send_mail_lock = false
47
+ @buff.clear
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ module Schleuder
2
+ class SchleuderLogger < Log4r::Logger
3
+ # Instantiates a logger to be used outside of list-contexts and sets
4
+ # outputters and level.
5
+ def initialize
6
+ # The name 'log4r' is special: it is considered as meta-logger by Log4r
7
+ # and receives log_internal()-messages.
8
+ super 'log4r'
9
+ # The initial log_level is inherited by all outputters.
10
+ @level = eval("Log4r::#{Schleuder.config.log_level.upcase}")
11
+ pattern = "%d Schleuder %l\t%M"
12
+ formatter = Log4r::PatternFormatter.new(:pattern => pattern)
13
+ add Log4r::FileOutputter.new('file',
14
+ { :level => @level,
15
+ :filename => Schleuder.config.log_file,
16
+ :formatter => formatter }
17
+ )
18
+ add MetaEmailOutputter.new('email',
19
+ {:to => Schleuder.config.superadminaddr,
20
+ :immediate_at => 'ERROR, FATAL'})
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,861 @@
1
+ module Schleuder
2
+ class Mail < TMail::Mail
3
+
4
+ # Schleuder::Member's the mail shall be sent to
5
+ attr_accessor :recipients
6
+
7
+ # Additional data that is to be stored into internally sent
8
+ # mails. Must be a Hash.
9
+ attr_accessor :metadata
10
+
11
+ # Indicator if the incoming mail was encrypted
12
+ attr_accessor :in_encrypted
13
+
14
+ # Indicator if (and by whom) the incoming mail was signed. Is +false+ or a
15
+ # GPGME::Signature.
16
+ attr_accessor :in_signed
17
+
18
+ attr_accessor :resend_to
19
+
20
+ # The incoming mail in raw form (string). Needed to verify detached
21
+ # signatures (see +decrypt!+)
22
+ attr_accessor :original_message
23
+
24
+ # Shall we consider this email to be sent by the list-admin?
25
+ attr_reader :from_admin
26
+
27
+ # Shall we consider this email to be sent by a list-member?
28
+ attr_reader :from_member
29
+
30
+ # internal message_id we'll use for all outgoing mails
31
+ @@schleuder_message_id = nil
32
+
33
+ def initialize(*data)
34
+ super(*data)
35
+ @resend_to = []
36
+ @metadata = {:resent_to => [], :notice => [], :warning => [], :error => []}
37
+ end
38
+
39
+ # Overwrites TMail.parse to store the original incoming data. We possibly
40
+ # need that to verify pgp-signatures (TMail changes headers which
41
+ # invalidates the signature)
42
+ def self.parse(string)
43
+ foo = super(string)
44
+ foo.original_message = string
45
+ foo
46
+ end
47
+
48
+ # Desctructivly decrypt/verify +self+.
49
+ def decrypt!
50
+ # Note: We don't recurse into nested Mime-parts. Only the first level
51
+ # will be touched
52
+ if self.multipart? && !self.type_param('protocol').nil? && self.type_param('protocol').match(/^application\/pgp.*/)
53
+ Schleuder.log.debug 'mail is pgp/mime-formatted'
54
+ if self.sub_type == 'signed'
55
+ Schleuder.log.debug 'mail is signed but not encrypted'
56
+ Schleuder.log.debug 'Parsing original_message to split content from signature'
57
+
58
+ # first, identify the boundary
59
+ bm = self.original_message.match(/boundary="?'?([^"';]*)/)
60
+ boundary = "--" + Regexp.escape(bm[1])
61
+ Schleuder.log.debug "Identified boundary: #{boundary}"
62
+ # next, find the signed string between the first and the second-last boundary
63
+ signed_string = self.original_message.match(/.*#{boundary}\r?\n(.*?)\r?\n#{boundary}.*?#{boundary}.*?/m)[1]
64
+ # Add CRs, which probably have been stripped by the MTA
65
+ signed_string.gsub!(/\n/, "\r\n") unless signed_string.include?("\r")
66
+ Schleuder.log.debug "Identified signed string"
67
+ # third, find the pgp-signature
68
+ signature = self.original_message.match(/.*(-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*/m)[1] rescue ''
69
+ Schleuder.log.debug "Identified signature"
70
+ # finally, verify
71
+ Schleuder.log.info "Verifying"
72
+ foo, self.in_signed = self.crypt.verify(signature, signed_string)
73
+ plaintext = self.parts[0].to_s
74
+ elsif self.sub_type == 'encrypted'
75
+ Schleuder.log.debug 'mail is encrypted (and possibly signed)'
76
+ plaintext, self.in_encrypted, self.in_signed = self.crypt.decrypt(self.parts[1].body)
77
+ else
78
+ Schleuder.log.warn "Strange: message claims to be pgp/mime but neither to be signed nor encrypted"
79
+ end
80
+ # replace encrypted content with cleartext content
81
+ plainmsg = Mail.parse(plaintext)
82
+ #Schleuder.log.debug "plainmsg: #{plainmsg.to_s.inspect}"
83
+ # test for signed content within the previously encrypted content
84
+ if plainmsg._content_type_stripped == 'multipart/signed'
85
+ Schleuder.log.debug "Found signed message as cleartext, recurseing once"
86
+ plainmsg.original_message = plaintext
87
+ plainmsg.decrypt!
88
+ Schleuder.log.debug "End of recursion"
89
+ self.in_signed = plainmsg.in_signed
90
+ end
91
+ # get headers and body(parts) into self
92
+ if plainmsg.multipart?
93
+ Schleuder.log.debug "plainmsg.multipart? => true"
94
+ self.parts.clear
95
+ plainmsg.parts.each do |p|
96
+ self.parts.push(Mail.parse(p.to_s))
97
+ end
98
+ else
99
+ Schleuder.log.debug "plainmsg.multipart? => false"
100
+ self.body = plainmsg.body
101
+ end
102
+ self.content_type = plainmsg._content_type
103
+ self.disposition = plainmsg._disposition
104
+ else
105
+ Schleuder.log.debug 'no pgp-mime found, looking for pgp-inline'
106
+ # Do we simply push everything to crypt as that should return plain
107
+ # text if it is feeded plain text? Or does crypt return only an error
108
+ # if there is nothing to do for it?
109
+ if self.multipart?
110
+ Schleuder.log.debug 'multipart found, looking for pgp content in each part'
111
+ self.parts.each do |part|
112
+ _decrypt_pgp_inline(part)
113
+ end
114
+ else
115
+ Schleuder.log.debug 'single inline content found, looking for pgp content'
116
+ _decrypt_pgp_inline(self)
117
+ end
118
+ end
119
+ _parse_signature
120
+ end
121
+
122
+ # Destructivly encrypt and sign. +receiver+ must be a string or a Schleuder::Member
123
+ def encrypt!(receiver)
124
+ if receiver.kind_of?(String)
125
+ receiver = Member.new({'email' => receiver})
126
+ elsif ! receiver.kind_of?(Member)
127
+ raise "need a Member-object as argument, got '#{receiver.inspect}'"
128
+ end
129
+
130
+ Schleuder.log.info "Encrypting for #{receiver.inspect}"
131
+
132
+ key, msg = receiver.key
133
+ if key == false
134
+ Schleuder.log.warn msg.capitalize
135
+ return [false, msg]
136
+ elsif key.nil?
137
+ Schleuder.log.warn "No public key found for '#{keystring}'"
138
+ return [false, 'no public key found']
139
+ end
140
+
141
+ # choose pgp-variant from addresses value oder config.default_mime
142
+ pgpvariant = receiver.mime || Schleuder.list.config.default_mime
143
+
144
+ case pgpvariant.upcase
145
+ when 'PLAIN'
146
+ Schleuder.log.debug "encrypting as text/plain"
147
+ if self.multipart?
148
+ self.parts.each_with_index do |p,i|
149
+ self.parts[i] = _encrypt_pgp_inline(p, receiver)
150
+ end
151
+ self.content_type = 'multipart/mixed'
152
+ self.encoding = nil
153
+ else
154
+ foo = _encrypt_pgp_inline(self, receiver)
155
+ self.body = foo.body
156
+ foo.header.each do |k,v|
157
+ self[k] = v.to_s rescue nil
158
+ end
159
+ end
160
+ when 'MIME'
161
+ Schleuder.log.debug "encrypting pgp-mime"
162
+ gpgcontent = Mail.new
163
+ if self.multipart?
164
+ self.parts.each do |p|
165
+ gpgcontent.parts.push(p)
166
+ end
167
+ else
168
+ gpgcontent.body = self.body
169
+ gpgcontent.content_type = self._content_type
170
+ gpgcontent.charset = self.charset || 'UTF-8'
171
+ end
172
+
173
+ gpgcontainer = _new_part(crypt.encrypt_str(gpgcontent.to_s, receiver), 'application/octet-stream', self.charset)
174
+ gpgcontainer.set_disposition('inline', {:filename => 'message.asc'})
175
+
176
+ mimecontainer = _new_part("Version: 1\n", 'application/pgp-encrypted')
177
+ mimecontainer.disposition = 'attachment'
178
+
179
+ self.encoding = nil
180
+ self.body = ""
181
+ self.parts.clear
182
+ self.parts.push(mimecontainer)
183
+ self.parts.push(gpgcontainer)
184
+ self.set_content_type('multipart', 'encrypted', {:protocol => 'application/pgp-encrypted'})
185
+ self.mime_version = 1.0
186
+
187
+ when 'APPL'
188
+ Schleuder.log.error "mime-setting 'APPL' is deprecated, use 'MIME' instead for user '#{receiver.email}'"
189
+ else
190
+ Schleuder.log.error "Unknown mime-setting for user '#{receiver.email}': #{pgpvariant}"
191
+ end
192
+ return true
193
+ end
194
+
195
+ # Clearsign the message. Returns a string (raw msg, which must not be
196
+ # changed anymore) or a Schleuder::Mail.
197
+ def sign
198
+ Schleuder.log.debug "signing message"
199
+ if (self.to || []).length != 1
200
+ Schleuder.log.error "I need exactly one recipient in To-header. Found this: #{self.to.inspect}"
201
+ return false
202
+ end
203
+ member = Schleuder.list.find_member_by_email(self.to.to_s)
204
+ # ugly but necessary: member might be false and member.mime might be nil
205
+ pgpvariant = member.mime || Schleuder.list.config.default_mime rescue Schleuder.list.config.default_mime
206
+ case pgpvariant.upcase
207
+ when 'MIME'
208
+ Schleuder.log.debug "signing MIME"
209
+ gpgcontainer = Mail.new
210
+ if self.multipart?
211
+ self.parts.each do |p|
212
+ if p.main_type.eql?('text')
213
+ # Encode QP (there might be trailing white space
214
+ p.body = p.body.to_s.split("\n").map do |line|
215
+ [line].pack("M")
216
+ end.join("\n")
217
+ p.encoding = 'Quoted-Printable'
218
+ end
219
+ gpgcontainer.parts.push(p)
220
+ end
221
+ self.parts.clear
222
+ else
223
+ # Encode QP (there might be trailing white space which is illegal
224
+ # according to RFC 3156).
225
+ # Don't call body() as that returns the decoded string
226
+ gpgcontainer.body = body.to_s.split("\n").map do |line|
227
+ [line].pack("M")
228
+ end.join("\n")
229
+ gpgcontainer.encoding = 'Quoted-Printable'
230
+ gpgcontainer.content_type = self._content_type
231
+ gpgcontainer.disposition = self._disposition
232
+ self.body = ''
233
+ end
234
+ self.encoding = ''
235
+ self.parts.push gpgcontainer
236
+
237
+ # This following part is quite complicated. We have to do it this way
238
+ # because TMail changes the mime-boundary on every encoded() (implicit
239
+ # in to_s()), which invalidates the signature
240
+
241
+ # TODO: refactor with encrypt!()
242
+
243
+ # First we create the signature-attachment and fill it with a dummy
244
+ dummy = "dummytobereplaced-#{Time.now.to_f}"
245
+ sigpart = _new_part(dummy, 'application/pgp-signature')
246
+ self.parts.push(sigpart)
247
+
248
+ # TODO: take care of micalg: the digest used for hashing the plaintext.
249
+ # RFC 3156 requires it to be set in the content-type. ruby-gpgme
250
+ # doesn't provide it to us, though.
251
+ # (Don't use symbols with TMail, it expects strings.)
252
+ self.set_content_type('multipart', 'signed', {'protocol' => 'application/pgp-signature', 'micalg' => 'pgp-sha1'})
253
+
254
+ # Then we dump the crafted msg
255
+ rawmsg = self.encoded
256
+
257
+ # get mime boundary from raw mail
258
+ bm = rawmsg.match(/boundary="?'?([^"';\r\n]*)/)
259
+ boundary = "--" + Regexp.escape(bm[1])
260
+
261
+ # Now get the to be signed string from the raw message
262
+ parts = rawmsg.match(/(.*#{boundary}\r?\n)(.*\r?\n)(\r?\n#{boundary}.*?#{boundary}.*)/m)
263
+
264
+ if parts
265
+ # For some reason I don't manage to do this in one gsub-call... *snif*
266
+ tobesigned_crlf = parts[2].gsub(/\n/, "\r\n").gsub(/\r\r/, "\r")
267
+ rawmsg = "#{parts[1]}#{tobesigned_crlf}#{parts[3]}"
268
+
269
+ # sign the extracted part
270
+ sig = self.crypt.sign(tobesigned_crlf)
271
+
272
+ # replace dummy with signature
273
+ rawmsg.gsub!(/(.*)#{dummy}(.*?#{boundary}.*?)/m, "\\1#{sig}\\2")
274
+ rawmsg
275
+ else
276
+ Schleuder.log.error "Detection of mime parts in mail for #{self.to} with boundary #{bm} failed. This should not happen. -> Cannot sign outgoing mail."
277
+
278
+ false
279
+ end
280
+ when 'PLAIN'
281
+ Schleuder.log.debug "signing PLAIN"
282
+ if self.multipart?
283
+ self.parts.each_with_index do |p,i|
284
+ if p.disposition == 'attachment' || !p.disposition_param('filename').nil?
285
+ Schleuder.log.debug "found attachment, creating detached signature"
286
+ # attachment, need detached sig
287
+ container = Mail.new
288
+ container.parts.push Mail.parse(p.to_s)
289
+
290
+ sig = crypt.sign(p.body)
291
+ Schleuder.log.debug "sig: #{sig.inspect}"
292
+ sigpart = _new_part(sig, 'text/plain')
293
+ sigpart.set_disposition('attachment', {:filename => p.disposition_param('filename') + '.asc'})
294
+ container.parts.push Mail.parse(sigpart.to_s)
295
+
296
+ container.content_type = 'multipart/mixed'
297
+
298
+ self.parts[i] = Mail.parse(container.to_s)
299
+ else
300
+ p.body = crypt.clearsign(p.body)
301
+ end
302
+ end
303
+ else
304
+ self.body = crypt.clearsign(self.body)
305
+ end
306
+ self
307
+
308
+ else
309
+ Schleuder.log.error "Strange mime-setting: #{pgpvariant}. Don't know what to do with that, ignoring it."
310
+ self
311
+ end
312
+ end
313
+
314
+ # Create a new Schleuder::Mail-instance and copy/set selected headers.
315
+ def individualize(recv)
316
+ Schleuder.log.debug { "Individualizing mail for #{recv.inspect}" }
317
+ new = Mail.new
318
+ new.crypt = crypt
319
+ # add some headers we want to keep
320
+ new.content_type = self['content-type'].to_s if self['content-type']
321
+ new.disposition = self._disposition
322
+ new.encoding = self.encoding.to_s
323
+ new.subject = _quote_if_necessary(self.subject.to_s,'UTF-8')
324
+
325
+ new.message_id = Mail._schleuder_message_id
326
+
327
+ new.to = recv.email.to_s
328
+ new.date = Time.now
329
+ new.from = Schleuder.list.config.myaddr
330
+ new = _add_openpgp_header(new) if Schleuder.list.config.include_openpgp_header
331
+ if self.multipart?
332
+ self.parts.each do |p|
333
+ new.parts.push(Mail.parse(p.to_s))
334
+ end
335
+ else
336
+ new.body = self.body
337
+ end
338
+ new
339
+ end
340
+
341
+ def individualize_member(recv)
342
+ new = self.individualize(recv)
343
+ _message_ids(new) if Schleuder.list.config.keep_msgid
344
+ _list_headers(new) if Schleuder.list.config.include_list_headers
345
+ end
346
+
347
+ # Strips keywords from self and stores them in @+keywords+
348
+ def keywords
349
+ unless @keywords
350
+ @keywords = []
351
+ # we're looking for keywords only in the first mime-part
352
+ b = self
353
+ while b.multipart?
354
+ b = b.parts.first
355
+ end
356
+ # split to array to ease parsing
357
+ a = b.body.split("\n")
358
+
359
+ a.map! do |line|
360
+ if line.match(/^X-.*/)
361
+ Schleuder.log.debug "found keyword-line: #{line.chomp}"
362
+ key, val = $&.split(/:|: | /, 2)
363
+ keyword = key.slice(2..-1).strip
364
+ if !self.from_admin && Schleuder.list.config.keywords_admin_only.include?(keyword)
365
+ Schleuder.log.info "Keyword '#{keyword}' is listed as admin only and mail is not from admin, skipping"
366
+ self.metadata[:error] << "Keyword '#{keyword}' is configured as admin-only."
367
+ # return the line to have it stay in the body
368
+ line
369
+ else
370
+ # Split values to catch multiple values separated by comma or the like (deprecated).
371
+ values = val.to_s.strip.split(/[ ,;]+/)
372
+ values << ' ' if values.empty?
373
+ values.each do |v|
374
+ Schleuder.log.info "Storing keyword #{keyword} with value #{v.inspect}"
375
+ @keywords << {keyword => v.to_s.strip}
376
+ end
377
+ nil
378
+ end
379
+ elsif line.chomp.empty?
380
+ line
381
+ else
382
+ # break on the first non-empty and non-command line so we don't parse
383
+ # the whole message (could be much data)
384
+ break
385
+ end
386
+ end
387
+ # delete nil's (lines formerly containing X-Commands) from array
388
+ a.compact!
389
+ # rejoin to string
390
+ b.body = a.join("\n")
391
+ # The whole procedure makes TMail parse and reformat the message, so now
392
+ # it's decoded utf-8
393
+ b.charset = 'UTF-8'
394
+ b.encoding = nil
395
+ Schleuder.log.debug "Inspecting found keywords: #{@keywords.inspect}"
396
+ end
397
+ @keywords
398
+ end
399
+
400
+ def request?
401
+ @request ||= self.to.to_a.include?(Schleuder.list.request_addr)
402
+ end
403
+
404
+ def bounce?
405
+ self.to.to_a.include?(Schleuder.list.bounce_addr) || \
406
+ ( # empty Return-Path
407
+ ['<>'].include?(self['Return-path'].to_s) || \
408
+ ( # Auto-Submitted exists and does not equal 'no'
409
+ ! ["no", ""].include?(self['Auto-Submitted'].to_s) && \
410
+ self['X-Cron-Env'].nil? # cron mails are also autosubmitted
411
+ )
412
+ )
413
+ end
414
+
415
+ # +require+s all found plugins, tests SomePlugin.match and if that returns
416
+ # true runs SomePlugin.process
417
+ def process_plugins!
418
+ if self.keywords.empty?
419
+ Schleuder.log.info 'No keywords present, skipping plugins'
420
+ elsif File.directory? Schleuder.config.plugins_dir
421
+ replymsg = ""
422
+ # We might want to replace the object later, which we can't do with self.
423
+ mail = self
424
+ plugins = {:request => [], :list => []}
425
+ ptype = (self.request? ? :request : :list)
426
+ used_keywords = []
427
+ Plugin.signing_key(self)
428
+ Dir[Schleuder.config.plugins_dir + '/*_plugin.rb'].each do |plugfile|
429
+ Schleuder.log.debug "Instanciating #{plugfile} as plugin"
430
+ require plugfile
431
+ # interpreting class name from file name
432
+ classname = File.basename(plugfile, '.rb').split('_').collect { |p| p.capitalize }.join
433
+ plugin = instance_eval(classname).new
434
+ Schleuder.log.debug "Storing plugin in plugin-list '#{plugin.plugin_type}'"
435
+ plugins[plugin.plugin_type] << plugin
436
+ end
437
+ # This is neither elegant nor fast, but it's got to live only until the rewrite.
438
+ Schleuder.log.debug "Processing #{ptype}-plugins"
439
+ self.keywords.each do |kwhash|
440
+ keyword = kwhash.keys.first
441
+ value = kwhash.values.first
442
+ Schleuder.log.debug "Looping for keyword #{keyword}"
443
+ plugins[ptype].each do |plugin|
444
+ command = keyword.downcase.gsub(/-/, '_')
445
+ Schleuder.log.debug "Does #{plugin.class}.respond_to? '#{command}'"
446
+ if plugin.respond_to?(command)
447
+ Schleuder.log.debug "Yes it does, executing #{plugin.class}.#{command}"
448
+ used_keywords << keyword
449
+ foo = plugin.send(command, mail, value)
450
+ case foo
451
+ when String
452
+ Schleuder.log.debug "Method returned a string, saving it for reply to sender."
453
+ replymsg << foo << "\n\n\n"
454
+ when Mail
455
+ Schleuder.log.debug "Method returned a Mail-object, replacing myself by it."
456
+ mail = foo
457
+ else
458
+ Schleuder.log.debug "Method returned '#{foo.inspect}', don't know how to handle, skipping."
459
+ end
460
+ next
461
+ else
462
+ Schleuder.log.debug "No it doesn't."
463
+ end
464
+ end
465
+ # Generate error-message if no plugin was executed
466
+ unless used_keywords.include?(keyword)
467
+ msg = "No #{ptype}-plugin responded to keyword #{keyword}"
468
+ Schleuder.log.debug msg
469
+ if mail.request?
470
+ replymsg << "Error: #{msg}.\n\n"
471
+ else
472
+ mail.metadata[:error] << msg
473
+ end
474
+ end
475
+ end
476
+ # Decide how to go on: reply or list?
477
+ if mail.request?
478
+ Schleuder.log.debug "Message is a request, sending plugins-output back to sender."
479
+ if replymsg.empty?
480
+ replymsg = 'The keywords you sent did not produce any output. If you feel this is an error please contact the adminisrators of this list.'.fmt
481
+ end
482
+ # This exits.
483
+ Plugin.reply(mail, replymsg, used_keywords)
484
+ end
485
+ else
486
+ Schleuder.log.error "#{Schleuder.config.plugins_dir} does not exist or is not readable!"
487
+ end
488
+ end
489
+
490
+ def add_prefix!
491
+ # only add if it's not already present
492
+ unless self.subject.index(Schleuder.list.config.prefix)
493
+ Schleuder.log.debug "adding prefix"
494
+ self.subject = Schleuder.list.config.prefix + " " + self.subject.to_s
495
+ end
496
+ end
497
+
498
+ def add_prefix_in!
499
+ # only add if it's not already present
500
+ unless self.subject.index(Schleuder.list.config.prefix_in)
501
+ Schleuder.log.debug "adding prefix_in"
502
+ self.subject = Schleuder.list.config.prefix_in + " " + self.subject.to_s
503
+ end
504
+ end
505
+
506
+ def add_prefix_out!
507
+ # only add if it's not already present
508
+ unless self.subject.index(Schleuder.list.config.prefix_out)
509
+ Schleuder.log.debug "adding prefix_out"
510
+ self.subject = Schleuder.list.config.prefix_out + " " + self.subject.to_s
511
+ end
512
+ end
513
+
514
+ def add_metadata!
515
+ Schleuder.log.info "Adding meta-information on old mail to new mail"
516
+ Schleuder.log.debug 'Generating meta-information'
517
+ meta = ''
518
+ Schleuder.list.config.headers_to_meta.each do |h|
519
+ h = h.to_s.capitalize
520
+ val = self.header_string(h) rescue '(not parsable)'
521
+ next if val.nil?
522
+ val = TMail::Unquoter.unquote_and_convert_to(val,'utf-8')
523
+ meta << "#{h}: #{val}\n"
524
+ end
525
+ meta << "Enc: #{_enc_str @in_encrypted}\n"
526
+ meta << "Sig: #{_sig_str @in_signed}\n"
527
+ if self.multipart?
528
+ self.parts.each_with_index do |p,i|
529
+ unless p.in_encrypted.nil? && p.in_signed.nil?
530
+ meta << "part #{i+1}:\n"
531
+ meta << " enc: #{_enc_str p.in_encrypted}\n"
532
+ meta << " sig: #{_sig_str p.in_signed}\n"
533
+ end
534
+ end
535
+ end
536
+
537
+ Schleuder.log.debug 'Adding extra meta data'
538
+ @metadata.each do |name, content|
539
+ meta << "#{name.to_s.gsub(/_/, '-').capitalize}: #{(content.join('; ') || content.to_s)}\n" unless content.empty?
540
+ end
541
+ meta << "\n"
542
+
543
+ # insert oder prepend to the message
544
+ if self.first_part._content_type_stripped == 'text/plain'
545
+ Schleuder.log.debug "Glueing meta data into first part of message"
546
+ self.first_part.body = meta + self.first_part.body
547
+ # body is now utf-8 and decoded!
548
+ self.first_part.charset = 'UTF-8'
549
+ self.first_part.encoding = ''
550
+ else
551
+ # make the message multipart and prepend the meta-part
552
+ self.to_multipart! unless self.multipart?
553
+ Schleuder.log.debug "Prepending meta data as own mime part to message"
554
+ self.parts.unshift _new_part(meta, 'text/plain', 'UTF-8')
555
+ end
556
+
557
+ end
558
+
559
+ # Adds Schleuder::ListConfig.public_footer to the end of the body of self
560
+ # or the body of the first mimepart (if one of those is text/plain) or
561
+ # appends it as a new mimepart.
562
+ def add_public_footer!
563
+ if Schleuder.list.config.public_footer.strip.empty?
564
+ return false
565
+ end
566
+ Schleuder.log.debug "appending public footer"
567
+ footer = "\n\n-- \n#{Schleuder.list.config.public_footer}\n"
568
+
569
+ if self.first_part._content_type_stripped == 'text/plain'
570
+ self.first_part.body = self.first_part.body.to_s + footer
571
+ else
572
+ self.to_multipart! unless self.multipart?
573
+ self.parts.push _new_part(footer, 'text/plain', 'UTF-8')
574
+ end
575
+ end
576
+
577
+
578
+ def to_multipart!
579
+ # skip if already multipart
580
+ return false if self.multipart?
581
+ Schleuder.log.debug "Making message multipart"
582
+ # else move the body into a mime-part
583
+ p = _new_part(self.body, self['content-type'].to_s, self.charset, self.encoding)
584
+ p.disposition = self._disposition
585
+ self.parts.push p
586
+ self.body = ''
587
+ self.set_content_type 'multipart', 'mixed', {:boundary => TMail.new_boundary}
588
+ self.disposition = ''
589
+ end
590
+
591
+ def first_part
592
+ if self.multipart?
593
+ self.parts.first
594
+ else
595
+ self
596
+ end
597
+ end
598
+
599
+ def _content_type(default = 'text/plain')
600
+ (self['content-type'] || default).to_s rescue default
601
+ end
602
+
603
+ def _content_type_stripped(default = nil)
604
+ ( default && _content_type(default) || _content_type() ).to_s.split(';').first
605
+ end
606
+
607
+ def _disposition
608
+ (self['content-disposition'] || '').to_s rescue ''
609
+ end
610
+
611
+ def from_member_address?
612
+ from.all? { |a| Schleuder.list.find_member_by_address(a) }
613
+ end
614
+
615
+ def from_admin_address?
616
+ from.all? { |a| Schleuder.list.find_admin_by_address(a) }
617
+ end
618
+
619
+ # An instance of Schleuder::Crypt
620
+ attr_writer :crypt
621
+ def crypt
622
+ @crypt ||= Crypt.new(Schleuder.list.config.gpg_password)
623
+ end
624
+
625
+ private
626
+
627
+ def _decrypt_pgp_inline(msg)
628
+ if msg._content_type_stripped == 'text/html'
629
+ Schleuder.log.debug "Content-type is text/html, can't handle that, skipping"
630
+ elsif msg.body =~ /^-----BEGIN PGP.*/ || msg.body.content_type.split('/').last.eql?('pgp')
631
+ Schleuder.log.debug 'found pgp-inline in input'
632
+ # Look for charset-armor-header. In most cases useless but nobody shall say we didn't try.
633
+ msg.body.each_line do |l|
634
+ break if l.strip.empty?
635
+ next unless m = l.match(/^Charset:\s(.*)$/)
636
+ @charset = m[1]
637
+ end
638
+ # TMail decodes QP if body() is called, Umlauts if to_s() is called.
639
+ # Life with TMail is hard...
640
+ if msg.encoding.to_s.downcase.eql?('quoted-printable') || !msg.main_type.eql?('text')
641
+ str = msg.body
642
+ else
643
+ str = msg.to_s
644
+ end
645
+ # We need to do this in three steps:
646
+ # 1. get the decoded body from TMail
647
+ # 2. delete the encoding-header
648
+ # 3. put the plaintext back into TMail
649
+ # Else TMail either can't decode the body correctly or 'over-decodes' it
650
+ # on next output
651
+ ptxt, enc, sig = crypt.decrypt(str)
652
+ if msg.main_type.eql?('text')
653
+ msg.body = ptxt
654
+ msg.charset = @charset || 'UTF-8'
655
+ msg.encoding = nil
656
+ else
657
+ # base64-encode if not text as we don't know what's in there. Might
658
+ # be binary data, which could break otherweise.
659
+ msg.body = [ptxt].pack("m")
660
+ msg.encoding = 'base64'
661
+ end
662
+ msg.in_encrypted = enc
663
+ msg.in_signed = sig
664
+ # RFC 2440 defines that the armored text by default is utf-8
665
+ if msg.content_type && msg['content-type'].params['x-action'] =~ /^pgp/
666
+ msg['content-type'].params['x-action'] = nil
667
+ end
668
+ unless msg.disposition_param('filename').nil?
669
+ msg['content-disposition'].params['filename'] = msg.disposition_param('filename').gsub(/\.(gpg|pgp|asc)$/, '')
670
+ end
671
+ else
672
+ Schleuder.log.debug 'no pgp-inline-data found, doing nothing'
673
+ end
674
+ end
675
+
676
+ def _encrypt_pgp_inline(msg, receiver)
677
+ msg.body = crypt.encrypt_str(msg.body.to_s, receiver)
678
+ # reset encoding, TMail has converted the content
679
+ msg.encoding = nil
680
+ # if attachment add '.gpg'-suffix to attachments file names
681
+ # (The query for 'filename' is a work around against buggy client
682
+ # formatting that doesn't disposition attachments as attachments. This
683
+ # variant works ok.)
684
+ unless msg.disposition_param('filename').nil?
685
+ msg.set_content_type('application', 'octet-stream', {'x-action' => 'pgp-encrypted'})
686
+ msg.set_disposition('attachment', {:filename => msg.disposition_param('filename') + '.gpg'})
687
+ else
688
+ msg.set_content_type('text', 'plain', {'x-action' => 'pgp-encrypted'})
689
+ msg.charset = 'UTF-8'
690
+ end
691
+ msg
692
+ end
693
+
694
+ def _enc_str(arg)
695
+ arg ? 'encrypted' : 'unencrypted'
696
+ end
697
+
698
+ def _sig_str(arg)
699
+ if arg
700
+ if crypt.get_key(arg.fpr).first
701
+ arg.to_s
702
+ else
703
+ #Schleuder.log.debug arg.inspect
704
+ "Unknown signature from #{arg.fpr} (public key not present)"
705
+ end
706
+ else
707
+ 'No signature'
708
+ end
709
+ end
710
+
711
+ # Tests if the signature of the incoming mail (if any) belongs to a
712
+ # list-member or an admin by testing all gpg-key-ids of the signing
713
+ # key for matches
714
+ def _parse_signature
715
+ @from_admin = @from_member = false
716
+
717
+ Schleuder.log.debug 'Testing for valid signature'
718
+ if !in_signed
719
+ Schleuder.log.info 'No signature found'
720
+ elsif in_signed.status != 0
721
+ Schleuder.log.info 'Invalid or unknown signature found'
722
+ else
723
+ Schleuder.log.info 'Valid signature found'
724
+ key, msg = crypt.get_key(in_signed.fpr)
725
+ if key
726
+ Schleuder.log.debug "Testing key for matching some member's or admin's keys"
727
+ if Schleuder.list.find_admin_by_key(key)
728
+ @from_admin = true
729
+ end
730
+ if Schleuder.list.find_member_by_key(key)
731
+ @from_member = true
732
+ end
733
+ else
734
+ Schleuder.log.debug "Keylookup for signature failed! Reason: #{msg}"
735
+ end
736
+ end
737
+ end
738
+
739
+ # Convert the given text into quoted printable format, with an instruction
740
+ # that the text be eventually interpreted in the given charset.
741
+ def _quoted_printable(text, charset)
742
+ text = text.gsub( /[^a-z ]/i ) { _quoted_printable_encode($&) }.
743
+ gsub( / /, "_" )
744
+ "=?#{charset}?Q?#{text}?="
745
+ end
746
+
747
+ # Convert the given character to quoted printable format, taking into
748
+ # account multi-byte characters (if executing with $KCODE="u", for instance)
749
+ def _quoted_printable_encode(character)
750
+ result = ""
751
+ character.each_byte { |b| result << "=%02X" % b }
752
+ result
753
+ end
754
+
755
+ # A quick-and-dirty regexp for determining whether a string contains any
756
+ # characters that need escaping.
757
+ if !defined?(CHARS_NEEDING_QUOTING)
758
+ CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
759
+ end
760
+
761
+ # Quote the given text if it contains any "illegal" characters
762
+ def _quote_if_necessary(text, charset)
763
+ text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding)
764
+
765
+ (text =~ CHARS_NEEDING_QUOTING) ?
766
+ _quoted_printable(text, charset) :
767
+ text
768
+ end
769
+
770
+ # Helper for creating new message-parts
771
+ def _new_part(body, content_type, charset='', encoding='')
772
+ p = Mail.new
773
+ p.body = body.to_s
774
+ p.content_type = content_type
775
+ p.charset = charset.to_s unless charset.to_s.empty?
776
+ p.encoding = encoding.to_s unless encoding.to_s.empty?
777
+ p
778
+ end
779
+
780
+ def _collect_message_ids(ids)
781
+ return nil if ids.nil? || !ids.is_a?(Array) || ids.empty?
782
+ ids.select{ |id| Utils.schleuder_id?(id, Schleuder.list.listid) }
783
+ end
784
+
785
+ def self._schleuder_message_id
786
+ @@schleuder_message_id = Utils.generate_message_id(Schleuder.list.listid) unless @@schleuder_message_id
787
+ @@schleuder_message_id
788
+ end
789
+
790
+ def _message_ids(mail)
791
+ Schleuder.log.debug "Copying msgid to in-reply-to/references"
792
+ mail.in_reply_to = _collect_message_ids(self.in_reply_to)
793
+ mail.references = _collect_message_ids(self.references)
794
+ mail
795
+ end
796
+
797
+ def _list_headers(mail)
798
+ Schleuder.log.debug "Generating list-ids"
799
+ mail['List-Id'] = "<#{Schleuder.list.listid}>"
800
+ mail['List-Owner'] = _list_owner
801
+ mail['List-Post'] = _list_post
802
+ # TODO: adapt URL to version.
803
+ mail['List-Help'] = '<https://schleuder2.nadir.org/documentation.html>'
804
+ mail
805
+ end
806
+
807
+ def _list_owner
808
+ "<mailto:#{Schleuder.list.owner_addr}> (Use list's public key)"
809
+ end
810
+
811
+ def _list_post
812
+ if Schleuder.list.config.receive_admin_only
813
+ "NO (Admins only)"
814
+ elsif Schleuder.list.config.receive_authenticated_only
815
+ "<mailto:#{Schleuder.list.config.myaddr}> (Subscribers only)"
816
+ else
817
+ "<mailto:#{Schleuder.list.config.myaddr}>"
818
+ end
819
+ end
820
+
821
+ def _add_openpgp_header(mail)
822
+ Schleuder.log.debug "Add OpenPGP-Headers"
823
+ mail['OpenPGP'] = "id=#{Schleuder.list.key_fingerprint} "+
824
+ "(Send an email to #{Schleuder.list.sendkey_addr} to receive the public-key)"+
825
+ _gen_openpgp_pref_header
826
+ mail
827
+ end
828
+
829
+ def _gen_openpgp_pref_header
830
+ unless Schleuder.list.config.openpgp_header_preference == 'none'
831
+ pref_str = "; preference=#{Schleuder.list.config.openpgp_header_preference} ("
832
+ if Schleuder.list.config.receive_admin_only
833
+ pref_str << 'Only encrypted and signed emails by list-admins are accepted'
834
+ elsif !Schleuder.list.config.receive_authenticated_only
835
+ if Schleuder.list.config.receive_encrypted_only \
836
+ && Schleuder.list.config.receive_signed_only
837
+ pref_str << 'Only encrypted and signed emails are accepted'
838
+ elsif Schleuder.list.config.receive_encrypted_only \
839
+ && !Schleuder.list.config.receive_signed_only
840
+ pref_str << 'Only encrypted emails are accepted'
841
+ elsif !Schleuder.list.config.receive_encrypted_only \
842
+ && Schleuder.list.config.receive_signed_only
843
+ pref_str << 'Only signed emails are accepted'
844
+ else
845
+ pref_str << 'All kind of emails are accepted'
846
+ end
847
+ elsif Schleuder.list.config.receive_authenticated_only
848
+ if Schleuder.list.config.receive_encrypted_only
849
+ pref_str << 'Only encrypted and signed emails by list-members are accepted'
850
+ else
851
+ pref_str << 'Only signed emails by list-members are accepted'
852
+ end
853
+ else
854
+ pref_str << 'All kind of emails are accepted'
855
+ end
856
+ pref_str << ')'
857
+ end
858
+ pref_str || ''
859
+ end
860
+ end
861
+ end