schleuder 3.2.2 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -10
  3. data/Rakefile +16 -8
  4. data/bin/schleuder +2 -1
  5. data/bin/schleuder-api-daemon +3 -2
  6. data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +30 -0
  7. data/db/schema.rb +2 -2
  8. data/etc/list-defaults.yml +4 -2
  9. data/etc/schleuder.yml +11 -0
  10. data/lib/schleuder-api-daemon.rb +9 -354
  11. data/lib/schleuder-api-daemon/helpers/schleuder-api-daemon-helper.rb +143 -0
  12. data/lib/schleuder-api-daemon/routes/key.rb +40 -0
  13. data/lib/schleuder-api-daemon/routes/list.rb +69 -0
  14. data/lib/schleuder-api-daemon/routes/status.rb +5 -0
  15. data/lib/schleuder-api-daemon/routes/subscription.rb +99 -0
  16. data/lib/schleuder-api-daemon/routes/version.rb +5 -0
  17. data/lib/schleuder.rb +2 -3
  18. data/lib/schleuder/cli.rb +24 -0
  19. data/lib/schleuder/cli/subcommand_fix.rb +1 -1
  20. data/lib/schleuder/conf.rb +7 -1
  21. data/lib/schleuder/errors/active_model_error.rb +2 -5
  22. data/lib/schleuder/errors/decryption_failed.rb +2 -7
  23. data/lib/schleuder/errors/key_adduid_failed.rb +1 -5
  24. data/lib/schleuder/errors/key_generation_failed.rb +1 -8
  25. data/lib/schleuder/errors/keyword_admin_only.rb +1 -5
  26. data/lib/schleuder/errors/list_not_found.rb +1 -5
  27. data/lib/schleuder/errors/listdir_problem.rb +2 -7
  28. data/lib/schleuder/errors/loading_list_settings_failed.rb +2 -5
  29. data/lib/schleuder/errors/message_empty.rb +1 -5
  30. data/lib/schleuder/errors/message_not_from_admin.rb +2 -5
  31. data/lib/schleuder/errors/message_sender_not_subscribed.rb +2 -5
  32. data/lib/schleuder/errors/message_too_big.rb +2 -5
  33. data/lib/schleuder/errors/message_unauthenticated.rb +1 -4
  34. data/lib/schleuder/errors/message_unencrypted.rb +2 -5
  35. data/lib/schleuder/errors/message_unsigned.rb +2 -5
  36. data/lib/schleuder/errors/too_many_keys.rb +1 -8
  37. data/lib/schleuder/filters/{request_filter.rb → post_decryption/10_request.rb} +0 -0
  38. data/lib/schleuder/filters/{max_message_size.rb → post_decryption/20_max_message_size.rb} +0 -0
  39. data/lib/schleuder/filters/{forward_filter.rb → post_decryption/30_forward_to_owner.rb} +0 -0
  40. data/lib/schleuder/filters/post_decryption/40_receive_admin_only.rb +10 -0
  41. data/lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb +10 -0
  42. data/lib/schleuder/filters/post_decryption/60_receive_signed_only.rb +10 -0
  43. data/lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb +10 -0
  44. data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +10 -0
  45. data/lib/schleuder/filters/{bounces_filter.rb → pre_decryption/10_forward_bounce_to_admins.rb} +0 -0
  46. data/lib/schleuder/filters/{forward_incoming.rb → pre_decryption/20_forward_all_incoming_to_admins.rb} +0 -0
  47. data/lib/schleuder/filters/{send_key_filter.rb → pre_decryption/30_send_key.rb} +0 -0
  48. data/lib/schleuder/filters/{hotmail_message_filter.rb → pre_decryption/40_fix_exchange_messages.rb} +5 -3
  49. data/lib/schleuder/filters/{strip_alternative_filter.rb → pre_decryption/50_strip_html_from_alternative.rb} +1 -1
  50. data/lib/schleuder/filters_runner.rb +41 -31
  51. data/lib/schleuder/gpgme/ctx.rb +1 -1
  52. data/lib/schleuder/gpgme/import_status.rb +13 -7
  53. data/lib/schleuder/gpgme/key.rb +4 -0
  54. data/lib/schleuder/list.rb +7 -4
  55. data/lib/schleuder/mail/encrypted_part.rb +14 -0
  56. data/lib/schleuder/mail/gpg.rb +15 -0
  57. data/lib/schleuder/mail/message.rb +70 -30
  58. data/lib/schleuder/plugins/key_management.rb +32 -7
  59. data/lib/schleuder/plugins/subscription_management.rb +70 -3
  60. data/lib/schleuder/runner.rb +19 -8
  61. data/lib/schleuder/subscription.rb +5 -9
  62. data/lib/schleuder/validators/fingerprint_validator.rb +1 -1
  63. data/lib/schleuder/version.rb +1 -1
  64. data/locales/de.yml +96 -8
  65. data/locales/en.yml +102 -10
  66. metadata +48 -27
  67. data/lib/schleuder/errors/file_not_found.rb +0 -14
  68. data/lib/schleuder/errors/invalid_listname.rb +0 -13
  69. data/lib/schleuder/errors/list_exists.rb +0 -13
  70. data/lib/schleuder/errors/unknown_list_option.rb +0 -14
  71. data/lib/schleuder/filters/auth_filter.rb +0 -39
@@ -0,0 +1,143 @@
1
+ module SchleuderApiDaemonHelper
2
+ def valid_credentials?
3
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
4
+ if @auth.provided? && @auth.basic? && @auth.credentials.present?
5
+ username, api_key = @auth.credentials
6
+ username == 'schleuder' && Conf.api_valid_api_keys.include?(api_key)
7
+ else
8
+ false
9
+ end
10
+ end
11
+
12
+ def authenticate!
13
+ # Be careful to use path_info() — it can be changed by other filters!
14
+ return if request.path_info == '/status.json'
15
+ if ! valid_credentials?
16
+ headers['WWW-Authenticate'] = 'Basic realm="Schleuder API Daemon"'
17
+ halt 401, "Not authorized\n"
18
+ end
19
+ end
20
+
21
+ def list(id_or_email=nil)
22
+ if id_or_email.blank?
23
+ if params[:list_id].present?
24
+ id_or_email = params[:list_id]
25
+ else
26
+ client_error "Parameter list_id is required"
27
+ end
28
+ end
29
+ if is_an_integer?(id_or_email)
30
+ list = List.where(id: id_or_email).first
31
+ else
32
+ # list_id is actually an email address
33
+ list = List.where(email: id_or_email).first
34
+ end
35
+ list || halt(404)
36
+ end
37
+
38
+ def subscription(id_or_email)
39
+ if is_an_integer?(id_or_email)
40
+ sub = Subscription.where(id: id_or_email.to_i).first
41
+ else
42
+ # Email
43
+ if params[:list_id].blank?
44
+ client_error "Parameter list_id is required when using email as identifier for subscriptions."
45
+ else
46
+ sub = list.subscriptions.where(email: id_or_email).first
47
+ end
48
+ end
49
+ sub || halt(404)
50
+ end
51
+
52
+ def requested_list_id
53
+ # ActiveResource doesn't want to use query-params with create(), so here
54
+ # list_id might be included in the request-body.
55
+ params['list_id'] || parsed_body['list_id'] || client_error('Need list_id')
56
+ end
57
+
58
+ def parsed_body
59
+ @parsed_body ||= begin
60
+ b = JSON.parse(request.body.read)
61
+ logger.debug "parsed body: #{b.inspect}"
62
+ b
63
+ end
64
+ end
65
+
66
+ def server_error(msg)
67
+ logger.warn msg
68
+ halt(500, json(error: msg))
69
+ end
70
+
71
+ # TODO: unify error messages. This method currently sends an old error format. See <https://github.com/rails/activeresource/blob/d6a5186/lib/active_resource/base.rb#L227>.
72
+ def client_error(obj_or_msg, http_code=400)
73
+ text = case obj_or_msg
74
+ when String, Symbol
75
+ obj_or_msg.to_s
76
+ when ActiveRecord::Base
77
+ obj_or_msg.errors.full_messages
78
+ else
79
+ obj_or_msg
80
+ end
81
+ logger.error "Sending error to client: #{text.inspect}"
82
+ halt(http_code, json(errors: text))
83
+ end
84
+
85
+ # poor persons type casting
86
+ def cast_param_values
87
+ params.each do |key, value|
88
+ params[key] =
89
+ case value
90
+ when 'true' then true
91
+ when 'false' then false
92
+ when '0' then 0
93
+ when is_an_integer?(value) then value.to_i
94
+ else value
95
+ end
96
+ end
97
+ end
98
+
99
+ def key_to_hash(key, include_keydata=false)
100
+ hash = {
101
+ fingerprint: key.fingerprint,
102
+ email: key.email,
103
+ expiry: key.expires,
104
+ generated_at: key.generated_at,
105
+ primary_uid: key.primary_uid.uid,
106
+ oneline: key.oneline,
107
+ trust_issues: key.usability_issue
108
+ }
109
+ if include_keydata
110
+ hash[:description] = key.to_s
111
+ hash[:ascii] = key.armored
112
+ end
113
+ hash
114
+ end
115
+
116
+ def set_x_messages(messages)
117
+ if messages.present?
118
+ headers 'X-Messages' => Array(messages).join(' // ').gsub(/\n/, ' // ')
119
+ end
120
+ end
121
+
122
+ def find_key_material
123
+ key_material = parsed_body['key_material'].presence
124
+ # By convention key_material is either ASCII or base64-encoded.
125
+ if key_material && ! key_material.match('BEGIN PGP')
126
+ key_material = Base64.decode64(key_material)
127
+ end
128
+ key_material
129
+ end
130
+
131
+ def find_attributes_from_body(attribs)
132
+ Array(attribs).inject({}) do |memo, attrib|
133
+ if parsed_body.has_key?(attrib)
134
+ memo[attrib] = parsed_body[attrib]
135
+ end
136
+ memo
137
+ end
138
+ end
139
+
140
+ def is_an_integer?(input)
141
+ input.to_s.match(/^[0-9]+$/).present?
142
+ end
143
+ end
@@ -0,0 +1,40 @@
1
+ class SchleuderApiDaemon < Sinatra::Base
2
+ register Sinatra::Namespace
3
+
4
+ namespace '/keys' do
5
+ get '.json' do
6
+ keys = list.keys.sort_by(&:email).map do |key|
7
+ key_to_hash(key)
8
+ end
9
+ json keys
10
+ end
11
+
12
+ post '.json' do
13
+ input = parsed_body['keymaterial']
14
+ if ! input.match('BEGIN PGP')
15
+ input = Base64.decode64(input)
16
+ end
17
+ json list(requested_list_id).import_key(input)
18
+ end
19
+
20
+ get '/check_keys.json' do
21
+ json result: list.check_keys
22
+ end
23
+
24
+ get '/:fingerprint.json' do |fingerprint|
25
+ if key = list.key(fingerprint)
26
+ json key_to_hash(key, true)
27
+ else
28
+ 404
29
+ end
30
+ end
31
+
32
+ delete '/:fingerprint.json' do |fingerprint|
33
+ if list.delete_key(fingerprint)
34
+ 200
35
+ else
36
+ 404
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,69 @@
1
+ class SchleuderApiDaemon < Sinatra::Base
2
+ register Sinatra::Namespace
3
+
4
+ namespace '/lists' do
5
+ get '.json' do
6
+ json List.all, include: :subscriptions
7
+ end
8
+
9
+ post '.json' do
10
+ listname = parsed_body['email']
11
+ fingerprint = parsed_body['fingerprint']
12
+ adminaddress = parsed_body['adminaddress']
13
+ adminfingerprint = parsed_body['adminfingerprint']
14
+ adminkey = parsed_body['adminkey']
15
+ list, messages = ListBuilder.new({email: listname, fingerprint: fingerprint}, adminaddress, adminfingerprint, adminkey).run
16
+ if list.nil?
17
+ client_error(messages, 422)
18
+ elsif ! list.valid?
19
+ client_error(list, 422)
20
+ else
21
+ set_x_messages(messages)
22
+ body json(list)
23
+ end
24
+ end
25
+
26
+ get '/configurable_attributes.json' do
27
+ json(List.configurable_attributes) + "\n"
28
+ end
29
+
30
+ post '/send_list_key_to_subscriptions.json' do
31
+ json(result: list.send_list_key_to_subscriptions)
32
+ end
33
+
34
+ get '/new.json' do
35
+ json List.new
36
+ end
37
+
38
+ get '/:id.json' do |id|
39
+ json list(id)
40
+ end
41
+
42
+ put '/:id.json' do |id|
43
+ list = list(id)
44
+ if list.update(parsed_body)
45
+ 204
46
+ else
47
+ client_error(list)
48
+ end
49
+ end
50
+
51
+ patch '/:id.json' do |id|
52
+ list = list(id)
53
+ if list.update(parsed_body)
54
+ 204
55
+ else
56
+ client_error(list)
57
+ end
58
+ end
59
+
60
+ delete '/:id.json' do |id|
61
+ list = list(id)
62
+ if list.destroy
63
+ 200
64
+ else
65
+ client_error(list)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,5 @@
1
+ class SchleuderApiDaemon < Sinatra::Base
2
+ get '/status.json' do
3
+ json status: :ok
4
+ end
5
+ end
@@ -0,0 +1,99 @@
1
+ class SchleuderApiDaemon < Sinatra::Base
2
+ register Sinatra::Namespace
3
+
4
+ namespace '/subscriptions' do
5
+ get '.json' do
6
+ filterkeys = Subscription.configurable_attributes + [:list_id, :email]
7
+ filter = params.select do |param|
8
+ filterkeys.include?(param.to_sym)
9
+ end
10
+
11
+ logger.debug "Subscription filter: #{filter.inspect}"
12
+ if filter['list_id'] && ! is_an_integer?(filter['list_id'])
13
+ # Value is an email-address
14
+ if list = List.where(email: filter['list_id']).first
15
+ filter['list_id'] = list.id
16
+ else
17
+ status 404
18
+ return json(errors: 'No such list')
19
+ end
20
+ end
21
+
22
+ json Subscription.where(filter)
23
+ end
24
+
25
+ post '.json' do
26
+ begin
27
+ list = list(requested_list_id)
28
+ # We don't have to care about nil-values, subscribe() does that for us.
29
+ sub, msgs = list.subscribe(
30
+ parsed_body['email'],
31
+ parsed_body['fingerprint'],
32
+ parsed_body['admin'],
33
+ parsed_body['delivery_enabled'],
34
+ find_key_material
35
+ )
36
+ set_x_messages(msgs)
37
+ logger.debug "subcription: #{sub.inspect}"
38
+ if sub.valid?
39
+ logger.debug "Subscribed: #{sub.inspect}"
40
+ # TODO: why redirect instead of respond with result?
41
+ redirect to("/subscriptions/#{sub.id}.json"), 201
42
+ else
43
+ client_error(sub, 422)
44
+ end
45
+ rescue ActiveRecord::RecordNotUnique
46
+ logger.error "Already subscribed"
47
+ status 422
48
+ json errors: {email: ['is already subscribed']}
49
+ end
50
+ end
51
+
52
+ get '/configurable_attributes.json' do
53
+ json(Subscription.configurable_attributes) + "\n"
54
+ end
55
+
56
+ get '/new.json' do
57
+ json Subscription.new
58
+ end
59
+
60
+ get '/:id.json' do |id|
61
+ json subscription(id)
62
+ end
63
+
64
+ put '/:id.json' do |id|
65
+ sub = subscription(id)
66
+ list = sub.list
67
+ args = find_attributes_from_body(%w[email fingerprint admin delivery_enabled])
68
+ fingerprint, messages = list.import_key_and_find_fingerprint(find_key_material)
69
+ set_x_messages(messages)
70
+ # For an already existing subscription, only update fingerprint if a
71
+ # new one has been selected from the upload.
72
+ if fingerprint.present?
73
+ args["fingerprint"] = fingerprint
74
+ end
75
+ if sub.update(args)
76
+ 200
77
+ else
78
+ client_error(sub, 422)
79
+ end
80
+ end
81
+
82
+ patch '/:id.json' do |id|
83
+ sub = subscription(id)
84
+ if sub.update(parsed_body)
85
+ 200
86
+ else
87
+ client_error(sub)
88
+ end
89
+ end
90
+
91
+ delete '/:id.json' do |id|
92
+ if sub = subscription(id).destroy
93
+ 200
94
+ else
95
+ client_error(sub)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,5 @@
1
+ class SchleuderApiDaemon < Sinatra::Base
2
+ get '/version.json' do
3
+ json version: Schleuder::VERSION
4
+ end
5
+ end
@@ -22,6 +22,8 @@ $:.unshift libdir
22
22
  # Monkeypatches
23
23
  require 'schleuder/mail/parts_list.rb'
24
24
  require 'schleuder/mail/message.rb'
25
+ require 'schleuder/mail/gpg.rb'
26
+ require 'schleuder/mail/encrypted_part.rb'
25
27
  require 'schleuder/gpgme/import_status.rb'
26
28
  require 'schleuder/gpgme/key.rb'
27
29
  require 'schleuder/gpgme/sub_key.rb'
@@ -46,9 +48,6 @@ Dir["#{libdir}/schleuder/plugins/*.rb"].each do |file|
46
48
  require file
47
49
  end
48
50
  require 'schleuder/filters_runner'
49
- Dir["#{libdir}/schleuder/filters/*.rb"].each do |file|
50
- require file
51
- end
52
51
  Dir["#{libdir}/schleuder/validators/*.rb"].each do |file|
53
52
  require file
54
53
  end
@@ -62,11 +62,13 @@ module Schleuder
62
62
  list.logger.notify_admin(msg, nil, I18n.t('check_keys'))
63
63
  end
64
64
  end
65
+ permission_notice
65
66
  end
66
67
 
67
68
  desc 'refresh_keys [list1@example.com]', "Refresh all keys of all list from the keyservers sequentially (one by one or on the passed list). (This is supposed to be run from cron weekly.)"
68
69
  def refresh_keys(list=nil)
69
70
  work_on_lists(:refresh_keys,list)
71
+ permission_notice
70
72
  end
71
73
 
72
74
  desc 'pin_keys [list1@example.com]', "Find keys for subscriptions without a pinned key and try to pin a certain key (one by one or based on the passed list)."
@@ -122,6 +124,7 @@ module Schleuder
122
124
  end
123
125
 
124
126
  say "Schleuder has been set up. You can now create a new list using `schleuder-cli`.\nWe hope you enjoy!"
127
+ permission_notice
125
128
  rescue => exc
126
129
  fatal exc.message
127
130
  end
@@ -257,6 +260,7 @@ Please notify the users and admins of this list of these changes.
257
260
  if messages.present?
258
261
  say messages.gsub(' // ', "\n")
259
262
  end
263
+ permission_notice
260
264
  rescue => exc
261
265
  fatal "#{exc}\n#{exc.backtrace.first}"
262
266
  end
@@ -330,5 +334,25 @@ Please notify the users and admins of this list of these changes.
330
334
  end
331
335
  end
332
336
 
337
+ # Make this class exit with code 1 in case of an error. See <https://github.com/erikhuda/thor/issues/244>.
338
+ def self.exit_on_failure?
339
+ true
340
+ end
341
+
342
+ def permission_notice
343
+ if Process.euid == 0
344
+ dirs = [Conf.lists_dir, Conf.listlogs_dir]
345
+ if Conf.database['adapter'] == 'sqlite3'
346
+ dirs << Conf.database['database']
347
+ end
348
+ dirs_sentence = dirs.uniq.map { |dir| enquote(dir) }.to_sentence
349
+ say "Warning: this process was run as root -- please make sure that all files in #{dirs_sentence} have correct file system permissions for the user that is running both, schleuder from the MTA and `schleuder-api-daemon`."
350
+ end
351
+ end
352
+
353
+ def enquote(string)
354
+ "\`#{string}\`"
355
+ end
356
+
333
357
  end
334
358
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module SubcommandFix
3
3
 
4
4
  # Fixing a bug in Thor where the actual subcommand wouldn't show up
5
- # with some invokations of the help-output.
5
+ # with some invocations of the help-output.
6
6
  def banner(task, namespace = true, subcommand = true)
7
7
  "#{basename} #{task.formatted_usage(self, true, subcommand).split(':').join(' ')}"
8
8
  end