rims 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,17 +2,15 @@
2
2
 
3
3
  module RIMS
4
4
  class Error < StandardError
5
- def self.suppress_2nd_error_at_resource_closing(logger: nil)
6
- if ($!) then
7
- begin
8
- yield
9
- rescue # not mask the first error
10
- logger.error($!) if logger
11
- nil
12
- end
13
- else
14
- yield
5
+ def self.trace_error_chain(exception)
6
+ return enum_for(:trace_error_chain, exception) unless block_given?
7
+
8
+ while (exception)
9
+ yield(exception)
10
+ exception = exception.cause
15
11
  end
12
+
13
+ nil
16
14
  end
17
15
  end
18
16
  end
@@ -68,6 +68,10 @@ module RIMS
68
68
  def get_plug_in(name)
69
69
  PLUG_IN[name] or raise KeyError, "not found a key-value store plug-in: #{name}"
70
70
  end
71
+
72
+ def plug_in_names
73
+ PLUG_IN.keys
74
+ end
71
75
  end
72
76
 
73
77
  def initialize
@@ -136,7 +136,10 @@ module RIMS
136
136
  retry
137
137
  rescue
138
138
  logger.error('unexpected error at a detached thread and give up to retry write-lock timeout error.')
139
- logger.error($!)
139
+ Error.trace_error_chain($!) do |exception|
140
+ logger.error(exception)
141
+ end
142
+
140
143
  $!
141
144
  end
142
145
  }
@@ -31,13 +31,19 @@ module RIMS
31
31
  end
32
32
  module_function :compile_wildcard
33
33
 
34
+ IO_DATA_DUMP = false # true for debug
35
+
34
36
  def io_data_log(str)
35
37
  s = '<'
36
38
  s << str.encoding.to_s
37
39
  if (str.ascii_only?) then
38
40
  s << ':ascii_only'
39
41
  end
40
- s << '> ' << str.inspect
42
+ if (IO_DATA_DUMP) then
43
+ s << '> ' << str.inspect
44
+ else
45
+ s << '> ' << str.bytesize.to_s << ' octets'
46
+ end
41
47
  end
42
48
  module_function :io_data_log
43
49
 
@@ -37,7 +37,9 @@ module RIMS
37
37
  atom_list = request_reader.read_command
38
38
  rescue
39
39
  logger.error('invalid client command.')
40
- logger.error($!)
40
+ Error.trace_error_chain($!) do |exception|
41
+ logger.error(exception)
42
+ end
41
43
  response_write.call([ "* BAD client command syntax error\r\n" ])
42
44
  next
43
45
  end
@@ -46,7 +48,16 @@ module RIMS
46
48
 
47
49
  tag, command, *opt_args = atom_list
48
50
  logger.info("client command: #{tag} #{command}")
49
- logger.debug("client command parameter: #{opt_args.inspect}") if logger.debug?
51
+ if (logger.debug?) then
52
+ case (command.upcase)
53
+ when 'LOGIN'
54
+ log_opt_args = opt_args.dup
55
+ log_opt_args[-1] = '********'
56
+ else
57
+ log_opt_args = opt_args
58
+ end
59
+ logger.debug("client command parameter: #{log_opt_args.inspect}")
60
+ end
50
61
 
51
62
  begin
52
63
  case (command.upcase)
@@ -126,7 +137,9 @@ module RIMS
126
137
  end
127
138
  rescue
128
139
  logger.error('unexpected error.')
129
- logger.error($!)
140
+ Error.trace_error_chain($!) do |exception|
141
+ logger.error(exception)
142
+ end
130
143
  response_write.call([ "#{tag} BAD unexpected error\r\n" ])
131
144
  end
132
145
 
@@ -139,7 +152,7 @@ module RIMS
139
152
 
140
153
  nil
141
154
  ensure
142
- Error.suppress_2nd_error_at_resource_closing(logger: logger) { decoder.cleanup }
155
+ decoder.cleanup
143
156
  end
144
157
 
145
158
  def initialize(auth, logger)
@@ -158,7 +171,9 @@ module RIMS
158
171
  rescue
159
172
  raise if ($!.class.name =~ /AssertionFailedError/)
160
173
  @logger.error('internal server error.')
161
- @logger.error($!)
174
+ Error.trace_error_chain($!) do |exception|
175
+ @logger.error(exception)
176
+ end
162
177
  res << "#{tag} BAD internal server error\r\n"
163
178
  end
164
179
  }
@@ -183,7 +198,9 @@ module RIMS
183
198
  rescue
184
199
  raise if ($!.class.name =~ /AssertionFailedError/)
185
200
  @logger.error('internal server error.')
186
- @logger.error($!)
201
+ Error.trace_error_chain($!) do |exception|
202
+ @logger.error(exception)
203
+ end
187
204
  yield([ "#{tag} BAD internal server error\r\n" ])
188
205
  end
189
206
  end
@@ -245,7 +262,7 @@ module RIMS
245
262
 
246
263
  class InitialDecoder < Decoder
247
264
  def initialize(mail_store_pool, auth, logger,
248
- mail_delivery_user: Server::DEFAULT[:mail_delivery_user],
265
+ mail_delivery_user: Service::DEFAULT_CONFIG.mail_delivery_user,
249
266
  write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
250
267
  **next_decoder_optional)
251
268
  super(auth, logger)
@@ -190,7 +190,7 @@ module RIMS
190
190
 
191
191
  def read_client_response_data_plain(inline_client_response_data_base64)
192
192
  if (inline_client_response_data_base64) then
193
- @logger.debug("authenticate command: inline client response data: #{inline_client_response_data_base64}") if @logger.debug?
193
+ @logger.debug("authenticate command: inline client response data: #{Protocol.io_data_log(inline_client_response_data_base64)}") if @logger.debug?
194
194
  Protocol.decode_base64(inline_client_response_data_base64)
195
195
  else
196
196
  read_client_response_data
@@ -0,0 +1,953 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'etc'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'logger/joint'
7
+ require 'pathname'
8
+ require 'riser'
9
+ require 'socket'
10
+ require 'yaml'
11
+
12
+ module RIMS
13
+ class Service
14
+ class Configuration
15
+ class << self
16
+ def stringify_symbol(collection)
17
+ case (collection)
18
+ when Hash
19
+ Hash[collection.map{|key, value| [ stringify_symbol(key), stringify_symbol(value) ] }]
20
+ when Array
21
+ collection.map{|i| stringify_symbol(i) }
22
+ else
23
+ case (value = collection)
24
+ when Symbol
25
+ value.to_s
26
+ else
27
+ value
28
+ end
29
+ end
30
+ end
31
+
32
+ def update(dest, other)
33
+ case (dest)
34
+ when Hash
35
+ unless (other.is_a? Hash) then
36
+ raise ArgumentError, 'hash can only be updated with hash.'
37
+ end
38
+ for key, value in other
39
+ dest[key] = update(dest[key], value)
40
+ end
41
+ dest
42
+ when Array
43
+ if (other.is_a? Array) then
44
+ dest.concat(other)
45
+ else
46
+ other
47
+ end
48
+ else
49
+ other
50
+ end
51
+ end
52
+
53
+ def get_configuration(collection, base_dir=nil)
54
+ if ((collection.key? 'configuration') && (collection.key? 'configuration_file')) then
55
+ raise KeyError, 'configuration conflict: configuration, configuraion_file'
56
+ end
57
+
58
+ if (collection.key? 'configuration') then
59
+ collection['configuration']
60
+ elsif (collection.key? 'configuration_file') then
61
+ configuration_file_path = Pathname(collection['configuration_file'])
62
+ if (configuration_file_path.relative?) then
63
+ base_dir or raise ArgumentError, 'need for base_dir.'
64
+ configuration_file_path = base_dir + configuration_file_path # expect base_dir to be Pathname
65
+ end
66
+ YAML.load_file(configuration_file_path.to_s)
67
+ else
68
+ {}
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize
74
+ @config = {}
75
+ end
76
+
77
+ def load(config, load_path=nil)
78
+ stringified_config = self.class.stringify_symbol(config)
79
+ if (stringified_config.key? 'base_dir') then
80
+ base_dir = Pathname(stringified_config['base_dir'])
81
+ if (load_path && base_dir.relative?) then
82
+ stringified_config['base_dir'] = Pathname(load_path) + base_dir
83
+ else
84
+ stringified_config['base_dir'] = base_dir
85
+ end
86
+ elsif (load_path) then
87
+ stringified_config['base_dir'] = Pathname(load_path)
88
+ end
89
+ self.class.update(@config, stringified_config)
90
+
91
+ self
92
+ end
93
+
94
+ # configuration example.
95
+ # required_features:
96
+ # - rims/qdbm
97
+ # - rims/passwd/ldap
98
+ # logging:
99
+ # file:
100
+ # path: rims.log
101
+ # shift_age: 10
102
+ # shift_size: 1048576
103
+ # level: debug
104
+ # datetime_format: %Y-%m-%d %H:%M:%S
105
+ # shift_period_suffix: %Y%m%d
106
+ # stdout:
107
+ # level: info
108
+ # datetime_format: %Y-%m-%d %H:%M:%S
109
+ # protocol:
110
+ # # default is not output.
111
+ # # to output, set the log level to info or less.
112
+ # path: protocol.log
113
+ # shift_age: 10
114
+ # shift_size: 1048576
115
+ # level: info
116
+ # datetime_format: %Y-%m-%d %H:%M:%S
117
+ # shift_period_suffix: %Y%m%d
118
+ # daemon:
119
+ # daemonize: true
120
+ # debug: false
121
+ # status_file: rims.pid
122
+ # server_polling_interval_seconds: 3
123
+ # server_privileged_user: nobody
124
+ # server_privileged_group: nogroup
125
+ # server:
126
+ # listen_address:
127
+ # # see `Riser::SocketAddress.parse' for address format
128
+ # type: tcp
129
+ # host: 0.0.0.0
130
+ # port: 143
131
+ # backlog: 64
132
+ # accept_polling_timeout_seconds: 0.1
133
+ # thread_num: 20
134
+ # thread_queue_size: 20
135
+ # thread_queue_polling_timeout_seconds: 0.1
136
+ # send_buffer_limit_size: 16384
137
+ # openssl:
138
+ # use_ssl: true
139
+ # ssl_context: |
140
+ # # this entry is evaluated in an anonymous ruby ​​module
141
+ # # including OpenSSL to initialize the SSLContext used
142
+ # # for TLS connection.
143
+ # # SSLContext object is stored at `_'.
144
+ # # Pathname object is stored at `base_dir'.
145
+ # _.cert = X509::Certificate.new((base_dir / "tls_cert" / "server_default.cert").read)
146
+ # _.key = PKey.read((base_dir / "tls_secret" / "server.priv_key").read)
147
+ # sni_tbl = {
148
+ # "imap.example.com" => SSLContext.new.tap{|c| c.key = _.key; c.cert = X509::Certificate.new((base_dir / "tls_cert" / "server_imap.cert").read) },
149
+ # "imap2.example.com" => SSLContext.new.tap{|c| c.key = _.key; c.cert = X509::Certificate.new((base_dir / "tls_cert" / "server_imap2.cert").read) },
150
+ # "imap3.example.com" => SSLContext.new.tap{|c| c.key = _.key; c.cert = X509::Certificate.new((base_dir / "tls_cert" / "server_imap3.cert").read) }
151
+ # }
152
+ # _.servername_cb = lambda{|ssl_socket, hostname| sni_tbl[hostname.downcase] }
153
+ # lock:
154
+ # read_lock_timeout_seconds: 30
155
+ # write_lock_timeout_seconds: 30
156
+ # cleanup_write_lock_timeout_seconds: 1
157
+ # storage:
158
+ # meta_key_value_store:
159
+ # type: qdbm_depot
160
+ # configuration:
161
+ # bnum 1200000
162
+ # use_checksum: true
163
+ # text_key_value_store:
164
+ # type: qdbm_curia
165
+ # configuration_file: text_kvs_config.yml
166
+ # use_checksum: true
167
+ # authentication:
168
+ # hostname: imap.example.com
169
+ # password_sources:
170
+ # - type: plain
171
+ # configuration:
172
+ # - user: alice
173
+ # pass: open sesame
174
+ # - user: bob
175
+ # pass: Z1ON0101
176
+ # - type: hash
177
+ # configuration_file: passwd_hash.yml
178
+ # - type: ldap
179
+ # configuration:
180
+ # ldap_uri: ldap://ldap.example.com/ou=user,o=example,dc=nodomain?uid?one?(memberOf=cn=imap,ou=group,o=example,dc=nodomain)
181
+ # authorization:
182
+ # mail_delivery_user: "#postman"
183
+ #
184
+ # backward compatibility for required_features.
185
+ # load_libraries:
186
+ # - rims/qdbm
187
+ # - rims/passwd/ldap
188
+ #
189
+ # backward compatibility for logging.
190
+ # log_file: rims.log
191
+ # log_level: debug
192
+ # log_shift_age: 10
193
+ # log_shift_size: 1048576
194
+ # log_stdout: info
195
+ #
196
+ # backward compatibility for daemon.
197
+ # process_privilege_user: nobody
198
+ # process_privilege_group: nogroup
199
+ #
200
+ # backward compatibility for server.
201
+ # imap_host: 0.0.0.0
202
+ # imap_port: 143
203
+ # ip_addr: 0.0.0.0
204
+ # ip_port: 143
205
+ # send_buffer_limit: 16384
206
+ #
207
+ # backward compatibility for lock.
208
+ # read_lock_timeout_seconds: 30
209
+ # write_lock_timeout_seconds: 30
210
+ # cleanup_write_lock_timeout_seconds: 1
211
+ #
212
+ # backward compatibility for storage.
213
+ # key_value_store_type: gdbm
214
+ # use_key_value_store_checksum: true
215
+ # meta_key_value_store:
216
+ # plug_in: qdbm_depot
217
+ # configuration:
218
+ # bnum 1200000
219
+ # use_checksum: true
220
+ # text_key_value_store:
221
+ # plug_in: qdbm_curia
222
+ # configuration_file: text_kvs_config.yml
223
+ # use_checksum: true
224
+ #
225
+ # backward compatibility for authentication.
226
+ # hostname: imap.example.com
227
+ # username: alice
228
+ # password: open sesame
229
+ # user_list:
230
+ # - user: alice
231
+ # pass: open sesame
232
+ # - user: bob
233
+ # pass: Z1ON0101
234
+ # authentication:
235
+ # - plug_in: plain
236
+ # configuration:
237
+ # - user: alice
238
+ # pass: open sesame
239
+ # - user: bob
240
+ # pass: Z1ON0101
241
+ # - plug_in: hash
242
+ # configuration_file: passwd_hash.yml
243
+ # - plug_in: ldap
244
+ # configuration:
245
+ # ldap_uri: ldap://ldap.example.com/ou=user,o=example,dc=nodomain?uid?one?(memberOf=cn=imap,ou=group,o=example,dc=nodomain)
246
+ #
247
+ # backward compatibility for authorization.
248
+ # mail_delivery_user: "#postman"
249
+ #
250
+ def load_yaml(path)
251
+ load(YAML.load_file(path), File.dirname(path))
252
+ self
253
+ end
254
+
255
+ def require_features
256
+ # built-in plug-in
257
+ require 'rims/gdbm_kvs'
258
+ require 'rims/passwd'
259
+
260
+ if (feature_list = get_required_features) then
261
+ for feature in feature_list
262
+ require(feature)
263
+ end
264
+ end
265
+
266
+ nil
267
+ end
268
+
269
+ def get_required_features
270
+ @config.dig('required_features') ||
271
+ @config.dig('load_libraries') || # for backward compatibility
272
+ []
273
+ end
274
+
275
+ def base_dir
276
+ @config['base_dir'] or raise KeyError, 'not defined base_dir.'
277
+ end
278
+
279
+ def get_configuration(collection)
280
+ if (@config.key? 'base_dir') then # to avoid an error with DEFAULT_CONFIG
281
+ self.class.get_configuration(collection, base_dir)
282
+ else
283
+ self.class.get_configuration(collection)
284
+ end
285
+ end
286
+ private :get_configuration
287
+
288
+ # return parameters for Logger.new
289
+ def make_file_logger_params
290
+ log_path = Pathname(@config.dig('logging', 'file', 'path') ||
291
+ @config.dig('log_file') || # for backward compatibility
292
+ 'rims.log')
293
+ if (log_path.relative?) then
294
+ if (@config.key? 'base_dir') then # to avoid an error with DEFAULT_CONFIG
295
+ log_path = base_dir + log_path
296
+ end
297
+ end
298
+ logger_params = [ log_path.to_s ]
299
+
300
+ shift_age = @config.dig('logging', 'file', 'shift_age') ||
301
+ @config.dig('log_shift_age') # for backward compatibility
302
+ shift_size = @config.dig('logging', 'file', 'shift_size') ||
303
+ @config.dig('log_shift_size') # for backward compatibility
304
+ if (shift_size) then
305
+ logger_params << (shift_age || 0)
306
+ logger_params << shift_size
307
+ elsif (shift_age) then
308
+ logger_params << shift_age
309
+ end
310
+
311
+ kw_args = {}
312
+ kw_args[:level] = @config.dig('logging', 'file', 'level') ||
313
+ @config.dig('log_level') || # for backward compatibility
314
+ 'info'
315
+ kw_args[:progname] = 'rims'
316
+ if (datetime_format = @config.dig('logging', 'file', 'datetime_format')) then
317
+ kw_args[:datetime_format] = datetime_format
318
+ end
319
+ if (shift_period_suffix = @config.dig('logging', 'file', 'shift_period_suffix')) then
320
+ kw_args[:shift_period_suffix] = shift_period_suffix
321
+ end
322
+ logger_params << kw_args
323
+
324
+ logger_params
325
+ end
326
+
327
+ # return parameters for Logger.new
328
+ def make_stdout_logger_params
329
+ logger_params = [ STDOUT ]
330
+
331
+ kw_args = {}
332
+ kw_args[:level] = @config.dig('logging', 'stdout', 'level') ||
333
+ @config.dig('log_stdout') || # for backward compatibility
334
+ 'info'
335
+ kw_args[:progname] = 'rims'
336
+ if (datetime_format = @config.dig('logging', 'stdout', 'datetime_format')) then
337
+ kw_args[:datetime_format] = datetime_format
338
+ end
339
+ logger_params << kw_args
340
+
341
+ logger_params
342
+ end
343
+
344
+ # return parameters for Logger.new
345
+ def make_protocol_logger_params
346
+ log_path = Pathname(@config.dig('logging', 'protocol', 'path') || 'protocol.log')
347
+ if (log_path.relative?) then
348
+ if (@config.key? 'base_dir') then # to avoid an error with DEFAULT_CONFIG
349
+ log_path = base_dir + log_path
350
+ end
351
+ end
352
+ logger_params = [ log_path.to_s ]
353
+
354
+ shift_age = @config.dig('logging', 'protocol', 'shift_age')
355
+ shift_size = @config.dig('logging', 'protocol', 'shift_size')
356
+ if (shift_size) then
357
+ logger_params << (shift_age || 0)
358
+ logger_params << shift_size
359
+ elsif (shift_age) then
360
+ logger_params << shift_age
361
+ end
362
+
363
+ kw_args = {}
364
+ kw_args[:level] = @config.dig('logging', 'protocol', 'level') || 'unknown'
365
+ kw_args[:progname] = 'rims'
366
+ if (datetime_format = @config.dig('logging', 'protocol', 'datetime_format')) then
367
+ kw_args[:datetime_format] = datetime_format
368
+ end
369
+ if (shift_period_suffix = @config.dig('logging', 'protocol', 'shift_period_suffix')) then
370
+ kw_args[:shift_period_suffix] = shift_period_suffix
371
+ end
372
+ logger_params << kw_args
373
+
374
+ logger_params
375
+ end
376
+
377
+ def daemonize?
378
+ daemon_config = @config['daemon'] || {}
379
+ if (daemon_config.key? 'daemonize') then
380
+ daemon_config['daemonize']
381
+ else
382
+ true
383
+ end
384
+ end
385
+
386
+ def daemon_name
387
+ 'rims'
388
+ end
389
+
390
+ def daemon_debug?
391
+ daemon_config = @config['daemon'] || {}
392
+ if (daemon_config.key? 'debug') then
393
+ daemon_config['debug']
394
+ else
395
+ false
396
+ end
397
+ end
398
+
399
+ def status_file
400
+ file_path = @config.dig('daemon', 'status_file') || 'rims.pid'
401
+ file_path = Pathname(file_path)
402
+ if (file_path.relative?) then
403
+ if (@config.key? 'base_dir') then # to avoid an error with DEFAULT_CONFIG
404
+ file_path = base_dir + file_path
405
+ end
406
+ end
407
+ file_path.to_s
408
+ end
409
+
410
+ def server_polling_interval_seconds
411
+ @config.dig('daemon', 'server_polling_interval_seconds') || 3
412
+ end
413
+
414
+ def server_restart_overlap_seconds
415
+ # to avoid resource conflict between the new server and the old server.
416
+ 0
417
+ end
418
+
419
+ def server_privileged_user
420
+ @config.dig('daemon', 'server_privileged_user') ||
421
+ @config.dig('process_privilege_user') # for backward compatibility
422
+ end
423
+
424
+ def server_privileged_group
425
+ @config.dig('daemon', 'server_privileged_group') ||
426
+ @config.dig('process_privilege_group') # for backward compatibility
427
+ end
428
+
429
+ def listen_address
430
+ if (socket_address = @config.dig('server', 'listen_address')) then
431
+ return socket_address
432
+ end
433
+
434
+ # for backward compatibility
435
+ host = @config.dig('imap_host') || @config.dig('ip_addr')
436
+ port = @config.dig('imap_port') || @config.dig('ip_port')
437
+ if (host || port) then
438
+ socket_adress = {
439
+ 'type' => 'tcp',
440
+ 'host' => host || '0.0.0.0',
441
+ 'port' => port || 1430
442
+ }
443
+ return socket_adress
444
+ end
445
+
446
+ # default
447
+ '0.0.0.0:1430'
448
+ end
449
+
450
+ def accept_polling_timeout_seconds
451
+ @config.dig('server', 'accept_polling_timeout_seconds') || 0.1
452
+ end
453
+
454
+ def process_num
455
+ # not yet supported multi-process server configuration.
456
+ 0
457
+ end
458
+
459
+ def process_queue_size
460
+ 20
461
+ end
462
+
463
+ def process_queue_polling_timeout_seconds
464
+ 0.1
465
+ end
466
+
467
+ def process_send_io_polling_timeout_seconds
468
+ 0.1
469
+ end
470
+
471
+ def thread_num
472
+ @config.dig('server', 'thread_num') || 20
473
+ end
474
+
475
+ def thread_queue_size
476
+ @config.dig('server', 'thread_queue_size') || 20
477
+ end
478
+
479
+ def thread_queue_polling_timeout_seconds
480
+ @config.dig('server', 'thread_queue_polling_timeout_seconds') || 0.1
481
+ end
482
+
483
+ def send_buffer_limit_size
484
+ @config.dig('server', 'send_buffer_limit_size') ||
485
+ @config.dig('send_buffer_limit') || # for backward compatibility
486
+ 1024 * 16
487
+ end
488
+
489
+ module SSLContextConfigAttribute
490
+ def ssl_context
491
+ @__ssl_context__
492
+ end
493
+
494
+ def base_dir
495
+ @__base_dir__
496
+ end
497
+
498
+ alias _ ssl_context
499
+
500
+ class << self
501
+ def new_module(ssl_context, base_dir)
502
+ _module = Module.new
503
+ _module.instance_variable_set(:@__ssl_context__, ssl_context)
504
+ _module.instance_variable_set(:@__base_dir__, base_dir)
505
+ _module.module_eval{
506
+ include OpenSSL
507
+ include OpenSSL::SSL
508
+ extend SSLContextConfigAttribute
509
+ }
510
+ _module
511
+ end
512
+
513
+ # methodized to isolate local variable scope.
514
+ def eval_config(_module, expr, filename='(eval_config)')
515
+ _module.module_eval(expr, filename)
516
+ end
517
+ end
518
+ end
519
+
520
+ def ssl_context
521
+ if (openssl_config = @config['openssl']) then
522
+ if (openssl_config.key? 'use_ssl') then
523
+ use_ssl = openssl_config['use_ssl']
524
+ else
525
+ use_ssl = openssl_config.key? 'ssl_context'
526
+ end
527
+
528
+ if (use_ssl) then
529
+ ssl_context = OpenSSL::SSL::SSLContext.new
530
+ if (ssl_config_expr = openssl_config['ssl_context']) then
531
+ anon_mod = SSLContextConfigAttribute.new_module(ssl_context, base_dir)
532
+ SSLContextConfigAttribute.eval_config(anon_mod, ssl_config_expr, 'ssl_context')
533
+ end
534
+
535
+ ssl_context
536
+ end
537
+ end
538
+ end
539
+
540
+ def read_lock_timeout_seconds
541
+ @config.dig('lock', 'read_lock_timeout_seconds') ||
542
+ @config.dig('read_lock_timeout_seconds') || # for backward compatibility
543
+ 30
544
+ end
545
+
546
+ def write_lock_timeout_seconds
547
+ @config.dig('lock', 'write_lock_timeout_seconds') ||
548
+ @config.dig('write_lock_timeout_seconds') || # for backward compatibility
549
+ 30
550
+ end
551
+
552
+ def cleanup_write_lock_timeout_seconds
553
+ @config.dig('lock', 'cleanup_write_lock_timeout_seconds') ||
554
+ @config.dig('cleanup_write_lock_timeout_seconds') || # for backward compatibility
555
+ 1
556
+ end
557
+
558
+ KeyValueStoreFactoryBuilderParams = Struct.new(:origin_type, :origin_config, :middleware_list)
559
+ class KeyValueStoreFactoryBuilderParams
560
+ def build_factory
561
+ builder = KeyValueStore::FactoryBuilder.new
562
+ builder.open{|name| origin_type.open_with_conf(name, origin_config) }
563
+ for middleware in middleware_list
564
+ builder.use(middleware)
565
+ end
566
+ builder.factory
567
+ end
568
+ end
569
+
570
+ def make_key_value_store_params(collection)
571
+ kvs_params = KeyValueStoreFactoryBuilderParams.new
572
+ kvs_params.origin_type = KeyValueStore::FactoryBuilder.get_plug_in(collection['type'] ||
573
+ collection['plug_in'] || # for backward compatibility
574
+ @config['key_value_store_type'] || # for backward compatibility
575
+ 'gdbm')
576
+ kvs_params.origin_config = get_configuration(collection)
577
+
578
+ if (collection.key? 'use_checksum') then
579
+ use_checksum = collection['use_checksum']
580
+ elsif (@config.key? 'use_key_value_store_checksum') then
581
+ use_checksum = @config['use_key_value_store_checksum'] # for backward compatibility
582
+ else
583
+ use_checksum = true # default
584
+ end
585
+
586
+ kvs_params.middleware_list = []
587
+ kvs_params.middleware_list << Checksum_KeyValueStore if use_checksum
588
+
589
+ kvs_params
590
+ end
591
+ private :make_key_value_store_params
592
+
593
+ def make_meta_key_value_store_params
594
+ make_key_value_store_params(@config.dig('storage', 'meta_key_value_store') ||
595
+ @config.dig('meta_key_value_store') || # for backward compatibility
596
+ {})
597
+ end
598
+
599
+ def make_text_key_value_store_params
600
+ make_key_value_store_params(@config.dig('storage', 'text_key_value_store') ||
601
+ @config.dig('text_key_value_store') || # for backward compatibility
602
+ {})
603
+ end
604
+
605
+ def make_key_value_store_path(mailbox_data_structure_version, unique_user_id)
606
+ if (mailbox_data_structure_version.empty?) then
607
+ raise ArgumentError, 'too short mailbox data structure version.'
608
+ end
609
+ if (unique_user_id.length <= 2) then
610
+ raise ArgumentError, 'too short unique user ID.'
611
+ end
612
+
613
+ bucket_dir_name = unique_user_id[0..1]
614
+ store_dir_name = unique_user_id[2..-1]
615
+
616
+ base_dir + mailbox_data_structure_version + bucket_dir_name + store_dir_name
617
+ end
618
+
619
+ def make_authentication
620
+ if ((@config.key? 'authentication') && (@config['authentication'].is_a? Hash)) then
621
+ auth_conf = @config['authentication']
622
+ else
623
+ auth_conf = {}
624
+ end
625
+
626
+ hostname = auth_conf['hostname'] ||
627
+ @config['hostname'] || # for backward compatibility
628
+ Socket.gethostname
629
+ auth = Authentication.new(hostname: hostname)
630
+
631
+ if (passwd_src_list = auth_conf['password_sources']) then
632
+ for passwd_src_conf in passwd_src_list
633
+ plug_in_name = passwd_src_conf['type'] or raise KeyError, 'not found a password source type.'
634
+ plug_in_config = get_configuration(passwd_src_conf)
635
+ passwd_src = Authentication.get_plug_in(plug_in_name, plug_in_config)
636
+ auth.add_plug_in(passwd_src)
637
+ end
638
+ end
639
+
640
+ # for backward compatibility
641
+ if (user_list = @config['user_list']) then
642
+ plain_src = Password::PlainSource.new
643
+ for pw in user_list
644
+ user = pw['user'] or raise KeyError, 'not found a user_list user.'
645
+ pass = pw['pass'] or raise KeyError, 'not found a user_list pass.'
646
+ plain_src.entry(user, pass)
647
+ end
648
+ auth.add_plug_in(plain_src)
649
+ end
650
+
651
+ # for backward compatibility
652
+ if (username = @config['username']) then
653
+ password = @config['password'] or raise KeyError, 'not found a password.'
654
+ plain_src = Password::PlainSource.new
655
+ plain_src.entry(username, password)
656
+ auth.add_plug_in(plain_src)
657
+ end
658
+
659
+ # for backward compatibility
660
+ if ((@config.key? 'authentication') && (@config['authentication'].is_a? Array)) then
661
+ plug_in_list = @config['authentication']
662
+ for plug_in_conf in plug_in_list
663
+ plug_in_name = plug_in_conf['plug_in'] or raise KeyError, 'not found an authentication plug_in.'
664
+ plug_in_config = get_configuration(plug_in_conf)
665
+ passwd_src = Authentication.get_plug_in(plug_in_name, plug_in_config)
666
+ auth.add_plug_in(passwd_src)
667
+ end
668
+ end
669
+
670
+ auth
671
+ end
672
+
673
+ def mail_delivery_user
674
+ @config.dig('authorization', 'mail_delivery_user') ||
675
+ @config.dig('mail_delivery_user') || # for backward compatibility
676
+ '#postman'
677
+ end
678
+ end
679
+
680
+ DEFAULT_CONFIG = Configuration.new
681
+
682
+ # default features
683
+ DEFAULT_CONFIG.require_features
684
+
685
+ # for read-only
686
+ class << DEFAULT_CONFIG
687
+ undef load
688
+ undef load_yaml
689
+ end
690
+
691
+ def initialize(config)
692
+ @config = config
693
+ end
694
+
695
+ using Logger::JointPlus
696
+
697
+ def setup(server, daemon: false)
698
+ file_logger_params = @config.make_file_logger_params
699
+ logger = Logger.new(*file_logger_params)
700
+
701
+ stdout_logger_params = @config.make_stdout_logger_params
702
+ unless (daemon && @config.daemonize?) then
703
+ logger += Logger.new(*stdout_logger_params)
704
+ end
705
+
706
+ protocol_logger_params = @config.make_protocol_logger_params
707
+ protocol_logger = Logger.new(*protocol_logger_params)
708
+
709
+ logger.info('preload libraries.')
710
+ Riser.preload
711
+ Riser.preload(RIMS)
712
+ Riser.preload(RIMS::Protocol)
713
+ @config.require_features
714
+
715
+ logger.info('setup server.')
716
+ server.accept_polling_timeout_seconds = @config.accept_polling_timeout_seconds
717
+ server.process_num = @config.process_num
718
+ server.process_queue_size = @config.process_queue_size
719
+ server.process_queue_polling_timeout_seconds = @config.process_queue_polling_timeout_seconds
720
+ server.process_send_io_polling_timeout_seconds = @config.process_send_io_polling_timeout_seconds
721
+ server.thread_num = @config.thread_num
722
+ server.thread_queue_size = @config.thread_queue_size
723
+ server.thread_queue_polling_timeout_seconds = @config.thread_queue_polling_timeout_seconds
724
+
725
+ ssl_context = @config.ssl_context
726
+
727
+ make_kvs_factory = lambda{|kvs_params, kvs_type|
728
+ kvs_factory = kvs_params.build_factory
729
+ return lambda{|mailbox_data_structure_version, unique_user_id, db_name|
730
+ kvs_path = @config.make_key_value_store_path(mailbox_data_structure_version, unique_user_id)
731
+ unless (kvs_path.directory?) then
732
+ logger.debug("make a directory: #{kvs_path}") if logger.debug?
733
+ kvs_path.mkpath
734
+ end
735
+ db_path = kvs_path + db_name
736
+ logger.debug("#{kvs_type} data key-value sotre path: #{db_path}") if logger.debug?
737
+ kvs_factory.call(db_path.to_s)
738
+ }, lambda{
739
+ logger.info("#{kvs_type} key-value store parameter: type=#{kvs_params.origin_type}")
740
+ logger.info("#{kvs_type} key-value store parameter: config=#{kvs_params.origin_config.to_json}")
741
+ kvs_params.middleware_list.each_with_index do |middleware, i|
742
+ logger.info("#{kvs_type} key-value store parameter: middleware[#{i}]=#{middleware}")
743
+ end
744
+ }
745
+ }
746
+
747
+ kvs_meta_open, kvs_meta_log = make_kvs_factory.call(@config.make_meta_key_value_store_params, 'meta')
748
+ kvs_text_open, kvs_text_log = make_kvs_factory.call(@config.make_text_key_value_store_params, 'text')
749
+ auth = @config.make_authentication
750
+ mail_store_pool = MailStore.build_pool(kvs_meta_open, kvs_text_open)
751
+
752
+ server.before_start{|server_socket|
753
+ logger.info('start server.')
754
+ for feature in @config.get_required_features
755
+ logger.info("required feature: #{feature}")
756
+ end
757
+ logger.info("file logging parameter: path=#{file_logger_params[0]}")
758
+ file_logger_params[1..-2].each_with_index do |value, i|
759
+ logger.info("file logging parameter: shift_args[#{i}]=#{value}")
760
+ end
761
+ for name, value in file_logger_params[-1]
762
+ logger.info("file logging parameter: #{name}=#{value}")
763
+ end
764
+ for name, value in stdout_logger_params[-1]
765
+ logger.info("stdout logging parameter: #{name}=#{value}")
766
+ end
767
+ logger.info("protocol logging parameter: path=#{protocol_logger_params[0]}")
768
+ protocol_logger_params[1..-2].each_with_index do |value, i|
769
+ logger.info("protocol logging parameter: shift_args[#{i}]=#{value}")
770
+ end
771
+ for name, value in protocol_logger_params[-1]
772
+ logger.info("protocol logging parameter: #{name}=#{value}")
773
+ end
774
+ logger.info("listen address: #{server_socket.local_address.inspect_sockaddr}")
775
+ privileged_user = Etc.getpwuid(Process.euid).name rescue ''
776
+ logger.info("server privileged user: #{privileged_user}(#{Process.euid})")
777
+ privileged_group = Etc.getgrgid(Process.egid).name rescue ''
778
+ logger.info("server privileged group: #{privileged_group}(#{Process.egid})")
779
+ logger.info("server parameter: accept_polling_timeout_seconds=#{server.accept_polling_timeout_seconds}")
780
+ logger.info("server parameter: process_num=#{server.process_num}")
781
+ logger.info("server parameter: process_queue_size=#{server.process_queue_size}")
782
+ logger.info("server parameter: process_queue_polling_timeout_seconds=#{server.process_queue_polling_timeout_seconds}")
783
+ logger.info("server parameter: process_send_io_polling_timeout_seconds=#{server.process_send_io_polling_timeout_seconds}")
784
+ logger.info("server parameter: thread_num=#{server.thread_num}")
785
+ logger.info("server parameter: thread_queue_size=#{server.thread_queue_size}")
786
+ logger.info("server parameter: thread_queue_polling_timeout_seconds=#{server.thread_queue_polling_timeout_seconds}")
787
+ logger.info("server parameter: send_buffer_limit_size=#{@config.send_buffer_limit_size}")
788
+ if (ssl_context) then
789
+ Array(ssl_context.alpn_protocols).each_with_index do |protocol, i|
790
+ logger.info("openssl parameter: alpn_protocols[#{i}]=#{protocol}")
791
+ end
792
+ logger.info("openssl parameter: alpn_select_cb=#{ssl_context.alpn_select_cb.inspect}") if ssl_context.alpn_select_cb
793
+ logger.info("openssl parameter: ca_file=#{ssl_context.ca_file}") if ssl_context.ca_file
794
+ logger.info("openssl parameter: ca_path=#{ssl_context.ca_path}") if ssl_context.ca_path
795
+ if (ssl_context.cert) then
796
+ ssl_context.cert.to_text.each_line do |line|
797
+ logger.info("openssl parameter: [cert] #{line.chomp}")
798
+ end
799
+ else
800
+ logger.warn('openssl parameter: not defined cert attribute.')
801
+ end
802
+ logger.info("openssl parameter: cert_store=#{ssl_context.cert_store.inspect}") if ssl_context.cert_store
803
+ Array(ssl_context.ciphers).each_with_index do |cipher, i|
804
+ logger.info("openssl parameter: ciphers[#{i}]=#{cipher.join(',')}")
805
+ end
806
+ Array(ssl_context.client_ca).each_with_index do |cert, i|
807
+ cert.to_text.each_line do |line|
808
+ logger.info("openssl parameter: client_ca[#{i}]: #{line.chomp}")
809
+ end
810
+ end
811
+ logger.info("openssl parameter: client_cert_cb=#{ssl_context.client_cert_cb.inspect}") if ssl_context.client_cert_cb
812
+ Array(ssl_context.extra_chain_cert).each_with_index do |cert, i|
813
+ cert.to_text.each_line do |line|
814
+ logger.info("openssl parameter: extra_chain_cert[#{i}]: #{line.chomp}")
815
+ end
816
+ end
817
+ if (ssl_context.key) then
818
+ logger.info("openssl parameter: key=#{ssl_context.key.inspect}")
819
+ if (logger.debug?) then
820
+ ssl_context.key.to_text.each_line do |line|
821
+ logger.debug("openssl parameter: [key] #{line.chomp}")
822
+ end
823
+ end
824
+ else
825
+ logger.warn('openssl parameter: not defined key attribute.')
826
+ end
827
+ Array(ssl_context.npn_protocols).each_with_index do |protocol, i|
828
+ logger.info("openssl parameter: npn_protocols[#{i}]=#{protocol}")
829
+ end
830
+ logger.info("openssl parameter: npn_select_cb=#{ssl_context.npn_select_cb.inspect}") if ssl_context.npn_select_cb
831
+ logger.info("openssl parameter: options=0x#{'%08x' % ssl_context.options}") if ssl_context.options
832
+ logger.info("openssl parameter: renegotiation_cb=#{ssl_context.renegotiation_cb.inspect}") if ssl_context.renegotiation_cb
833
+ logger.info("openssl parameter: security_level=#{ssl_context.security_level}")
834
+ logger.info("openssl parameter: servername_cb=#{ssl_context.servername_cb.inspect}") if ssl_context.servername_cb
835
+ logger.info("openssl parameter: session_cache_mode=0x#{'%08x' % ssl_context.session_cache_mode}")
836
+ logger.info("openssl parameter: session_cache_size=#{ssl_context.session_cache_size }")
837
+ logger.info("openssl parameter: session_get_cb=#{ssl_context.session_get_cb.inspect}") if ssl_context.session_get_cb
838
+ logger.info("openssl parameter: session_id_context=#{ssl_context.session_id_context}") if ssl_context.session_id_context
839
+ logger.info("openssl parameter: session_new_cb=#{ssl_context.session_new_cb.inspect}") if ssl_context.session_new_cb
840
+ logger.info("openssl parameter: session_remove_cb=#{ssl_context.session_remove_cb}") if ssl_context.session_remove_cb
841
+ logger.info("openssl parameter: ssl_timeout=#{ssl_context.ssl_timeout}") if ssl_context.ssl_timeout
842
+ logger.info("openssl parameter: tmp_dh_callback=#{ssl_context.tmp_dh_callback}") if ssl_context.tmp_dh_callback
843
+ logger.info("openssl parameter: verify_callback=#{ssl_context.verify_callback}") if ssl_context.verify_callback
844
+ logger.info("openssl parameter: verify_depth=#{ssl_context.verify_depth}") if ssl_context.verify_depth
845
+ logger.info("openssl parameter: verify_hostname=#{ssl_context.verify_hostname}") if ssl_context.verify_hostname
846
+ logger.info("openssl parameter: verify_mode=0x#{'%08x' % ssl_context.verify_mode}") if ssl_context.verify_mode
847
+ end
848
+ logger.info("lock parameter: read_lock_timeout_seconds=#{@config.read_lock_timeout_seconds}")
849
+ logger.info("lock parameter: write_lock_timeout_seconds=#{@config.write_lock_timeout_seconds}")
850
+ logger.info("lock parameter: cleanup_write_lock_timeout_seconds=#{@config.cleanup_write_lock_timeout_seconds}")
851
+ kvs_meta_log.call
852
+ kvs_text_log.call
853
+ logger.info("authentication parameter: hostname=#{auth.hostname}")
854
+ logger.info("authorization parameter: mail_delivery_user=#{@config.mail_delivery_user}")
855
+ }
856
+ # server.at_fork{}
857
+ # server.at_stop{}
858
+ server.at_stat{|info|
859
+ logger.info("stat: #{info.to_json}")
860
+ }
861
+ server.preprocess{
862
+ auth.start_plug_in(logger)
863
+ }
864
+ server.dispatch{|socket|
865
+ begin
866
+ begin
867
+ begin
868
+ begin
869
+ remote_address = socket.remote_address # the place where the remote socket is most likely not closed is here
870
+ logger.info("accept connection: #{remote_address.inspect_sockaddr}")
871
+ if (ssl_context) then
872
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
873
+ logger.info("start tls: #{ssl_socket.state}")
874
+ ssl_socket.accept
875
+ logger.info("accept tls: #{ssl_socket.state}")
876
+ ssl_socket.sync = true
877
+ stream = Riser::WriteBufferStream.new(ssl_socket, @config.send_buffer_limit_size)
878
+ else
879
+ stream = Riser::WriteBufferStream.new(socket, @config.send_buffer_limit_size)
880
+ end
881
+ stream = Riser::LoggingStream.new(stream, protocol_logger)
882
+ decoder = Protocol::Decoder.new_decoder(mail_store_pool, auth, logger,
883
+ mail_delivery_user: @config.mail_delivery_user,
884
+ read_lock_timeout_seconds: @config.read_lock_timeout_seconds,
885
+ write_lock_timeout_seconds: @config.write_lock_timeout_seconds,
886
+ cleanup_write_lock_timeout_seconds: @config.cleanup_write_lock_timeout_seconds)
887
+ Protocol::Decoder.repl(decoder, stream, stream, logger)
888
+ ensure
889
+ if (stream) then
890
+ stream.flush
891
+ end
892
+ end
893
+ ensure
894
+ if (ssl_socket) then
895
+ ssl_socket.close
896
+ logger.info("close tls: #{ssl_socket.state}")
897
+ end
898
+ end
899
+ ensure
900
+ socket.close
901
+ if (remote_address) then
902
+ logger.info("close connection: #{remote_address.inspect_sockaddr}")
903
+ else
904
+ logger.info('close connection.')
905
+ end
906
+ end
907
+ rescue
908
+ logger.error('interrupt connection with unexpected error.')
909
+ Error.trace_error_chain($!) do |exception|
910
+ logger.error(exception)
911
+ end
912
+ end
913
+ }
914
+ server.postprocess{
915
+ auth.stop_plug_in(logger)
916
+ }
917
+ server.after_stop{
918
+ logger.info('stop server.')
919
+ }
920
+
921
+ nil
922
+ end
923
+ end
924
+ end
925
+
926
+ if ($0 == __FILE__) then
927
+ require 'pp' if $DEBUG
928
+ require 'rims'
929
+
930
+ if (ARGV.length != 1) then
931
+ STDERR.puts "usage: #{$0} config.yml"
932
+ exit(1)
933
+ end
934
+
935
+ config = RIMS::Service::Configuration.new
936
+ config.load_yaml(ARGV[0])
937
+ pp config if $DEBUG
938
+
939
+ server = Riser::SocketServer.new
940
+ service = RIMS::Service.new(config)
941
+ service.setup(server)
942
+
943
+ Signal.trap(:INT) { server.signal_stop_forced }
944
+ Signal.trap(:TERM) { server.signal_stop_graceful }
945
+
946
+ listen_address = Riser::SocketAddress.parse(config.listen_address)
947
+ server.start(listen_address.open_server)
948
+ end
949
+
950
+ # Local Variables:
951
+ # mode: Ruby
952
+ # indent-tabs-mode: nil
953
+ # End: