schleuder 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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