rims 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 299a57cf042b4a2854a5e040af070bbef170044d8cedbac0c4587559018deb45
4
- data.tar.gz: f9f6f5a9654c9b84c7978c4d6aa15d6243150811ee7a70b6110ee1e40eb1753f
3
+ metadata.gz: 397075dab0a3fa9bdf66a3a3be278b2d1eb3f67f6e2b12e73dcd27bf37190e66
4
+ data.tar.gz: 8e0734fcdda1d88e9720cefea0c8a2464f4fd4ddb5cfd68c67bc56635c9ef6f3
5
5
  SHA512:
6
- metadata.gz: 8f7009d59c5a09874933e75a9cea109367fb8f1ef4f1a2718a19536df1055fe7f5e7a60392bc50cf14b964e103ee7c2aa43493b6bf6790c8cba8083605edf3f8
7
- data.tar.gz: 78c978afee91b8f5a7ef769a55d4a62d5b2e138d7ef8c6d9ddf5942a997ea772dc1aa317915f72973c42e6b77b7a2cd7acce8a8bae6f9f402d19268524e3b8dc
6
+ metadata.gz: 5b9fa09f69d35c243e8be71387fe29c4ab3b2237e2a530737e5108dba9371579c2fda3d9fa6bfa7bbebb0ffef3121e6e84863fa111c31b93d1ef8aeeb979c870
7
+ data.tar.gz: 8cb95a095d7f15f0fe6c7a7ac3bb6d4ec845e6950923c50dc69a93428c082576faaf0a48d83273a923d923dafc6a85eced6affb2c5039408f9b83add0cddd6e9
data/.gitignore CHANGED
@@ -1,17 +1,19 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /html/
7
+ /load_test/mails/
8
+ /load_test/imap_server/
9
+ /load_test/imap_append/
10
+ /load_test/post_mail/
11
+ /pkg/
12
+ /spec/reports/
13
+ /test/tls/
14
+ /tmp/
15
+ /vendor/
16
+ /Gemfile.lock
17
+ /README.html
18
+ *~
19
+ *.log
data/ChangeLog CHANGED
@@ -1,5 +1,41 @@
1
+ 2019-04-10 TOKI Yoshinori <toki@freedom.ne.jp>
2
+
3
+ * RIMS version 0.2.3 is released.
4
+
5
+ * lib/rims/cmd.rb, lib/rims/lock.rb, lib/rims/protocol/decoder.rb,
6
+ lib/rims/service.rb: trace all unexpected errors.
7
+
8
+ 2019-04-09 TOKI Yoshinori <toki@freedom.ne.jp>
9
+
10
+ * test/cmd/test_command.rb: command test.
11
+
12
+ 2019-04-06 TOKI Yoshinori <toki@freedom.ne.jp>
13
+
14
+ * the server framework is replaced to riser and delete old server
15
+ framework.
16
+
17
+ 2019-03-26 TOKI Yoshinori <toki@freedom.ne.jp>
18
+
19
+ * lib/rims/service.rb: service and configuration for
20
+ riser. implemented backward compatibility configuration.
21
+
22
+ 2019-03-18 TOKI Yoshinori <toki@freedom.ne.jp>
23
+
24
+ * lib/rims/service.rb: service and configuration for
25
+ riser. implemented the full parameters to run the server.
26
+
27
+ 2019-03-10 TOKI Yoshinori <toki@freedom.ne.jp>
28
+
29
+ * lib/rims/service.rb: service and configuration for
30
+ riser. implemented the minimum necessary to run the server.
31
+
1
32
  2019-03-06 TOKI Yoshinori <toki@freedom.ne.jp>
2
33
 
34
+ * rims.gemspec: add riser to runtime dependency.
35
+ RISER is a library of Ruby Infrastructure for cooperative
36
+ multi-thread/multi-process SERver. the framework of the server is
37
+ planned to replace with riser.
38
+
3
39
  * RIMS version 0.2.2 is released.
4
40
 
5
41
  2019-03-03 TOKI Yoshinori <toki@freedom.ne.jp>
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  require 'bundler/gem_tasks'
4
+ require 'pathname'
4
5
  require 'rake/clean'
5
6
  require 'rake/testtask'
6
7
  require 'rdoc/task'
@@ -11,11 +12,23 @@ Rake::TestTask.new do |task|
11
12
  end
12
13
  end
13
14
 
15
+ Rake::TestTask.new(:test_cmd) do |task|
16
+ task.description = 'Run tests for rims command'
17
+ task.pattern = 'test/cmd/test*.rb'
18
+ task.options = '-v'
19
+ if ((ENV.key? 'RUBY_DEBUG') && (! ENV['RUBY_DEBUG'].empty?)) then
20
+ task.ruby_opts << '-d'
21
+ end
22
+ end
23
+
24
+ desc 'Run all tests'
25
+ task :test_all => [ :test, :test_cmd ]
26
+
14
27
  Rake::RDocTask.new do |rd|
15
28
  rd.rdoc_files.include('lib/**/*.rb')
16
29
  end
17
30
 
18
- desc 'Build README.html from markdown source.'
31
+ desc 'Build README.html from markdown source'
19
32
  task :readme => %w[ README.html ]
20
33
 
21
34
  file 'README.html' => [ 'README.md' ] do
@@ -23,6 +36,62 @@ file 'README.html' => [ 'README.md' ] do
23
36
  end
24
37
  CLOBBER.include 'README.html'
25
38
 
39
+ namespace :test_cert do
40
+ tls_dir = Pathname('test/tls')
41
+
42
+ directory tls_dir.to_path
43
+ CLOBBER.include tls_dir.to_path
44
+
45
+ desc 'Delete TLS certificate files for test'
46
+ task :delete do
47
+ rm_rf tls_dir.to_path
48
+ end
49
+
50
+ ca_priv_key = tls_dir / 'ca.priv_key'
51
+ ca_cert_sign_req = tls_dir / 'ca.cert_sign_req'
52
+ ca_cert = tls_dir / 'ca.cert'
53
+ server_priv_key = tls_dir / 'server.priv_key'
54
+ server_localhost_cert_sign_req = tls_dir / 'server_localhost.cert_sign_req'
55
+ server_localhost_cert = tls_dir / 'server_localhost.cert'
56
+
57
+ file ca_priv_key.to_path => [ tls_dir ].map(&:to_path) do
58
+ sh "openssl genrsa 2048 >#{ca_priv_key}"
59
+ end
60
+
61
+ file ca_cert_sign_req.to_path => [ tls_dir, ca_priv_key ].map(&:to_path) do
62
+ sh "openssl req -new -key #{ca_priv_key} -sha256 -subj '/C=JP/ST=Tokyo/L=Tokyo/O=Private/OU=Home/CN=*' >#{ca_cert_sign_req}"
63
+ end
64
+
65
+ file ca_cert.to_path => [ tls_dir, ca_priv_key, ca_cert_sign_req ].map(&:to_path) do
66
+ sh "openssl x509 -req -signkey #{ca_priv_key} -sha256 -days 3650 <#{ca_cert_sign_req} >#{ca_cert}"
67
+ end
68
+
69
+ file server_priv_key.to_path => [ tls_dir ].map(&:to_path) do
70
+ sh "openssl genrsa 2048 >#{server_priv_key}"
71
+ end
72
+
73
+ file server_localhost_cert_sign_req.to_path => [ tls_dir, server_priv_key ].map(&:to_path) do
74
+ sh "openssl req -new -key #{server_priv_key} -sha256 -subj '/C=JP/ST=Tokyo/L=Tokyo/O=Private/OU=Home/CN=localhost' >#{server_localhost_cert_sign_req}"
75
+ end
76
+
77
+ file server_localhost_cert.to_path => [ tls_dir, ca_cert, ca_priv_key, server_localhost_cert_sign_req ].map(&:to_path) do
78
+ sh "openssl x509 -req -CA #{ca_cert} -CAkey #{ca_priv_key} -CAcreateserial -sha256 -days 3650 <#{server_localhost_cert_sign_req} >#{server_localhost_cert}"
79
+ end
80
+
81
+ desc 'Make TLS certificate files for test'
82
+ task :make => [ ca_priv_key, ca_cert, server_priv_key, server_localhost_cert ].map(&:to_path)
83
+
84
+ desc 'Show TLS certificate files for test'
85
+ task :show => :make do
86
+ sh "openssl rsa -text -noout <#{ca_priv_key}"
87
+ sh "openssl req -text -noout <#{ca_cert_sign_req}"
88
+ sh "openssl x509 -text -noout <#{ca_cert}"
89
+ sh "openssl rsa -text -noout <#{server_priv_key}"
90
+ sh "openssl req -text -noout <#{server_localhost_cert_sign_req}"
91
+ sh "openssl x509 -text -noout <#{server_localhost_cert}"
92
+ end
93
+ end
94
+
26
95
  # Local Variables:
27
96
  # mode: Ruby
28
97
  # indent-tabs-mode: nil
@@ -2,14 +2,13 @@
2
2
 
3
3
  require "rims/version"
4
4
 
5
+ autoload :OpenSSL, 'openssl'
6
+
5
7
  module RIMS
6
8
  autoload :Authentication, 'rims/auth'
7
- autoload :BufferedWriter, 'rims/server'
8
9
  autoload :Checksum_KeyValueStore, 'rims/cksum_kvs'
9
10
  autoload :Cmd, 'rims/cmd'
10
- autoload :Config, 'rims/server'
11
11
  autoload :DB, 'rims/db'
12
- autoload :Daemon, 'rims/daemon'
13
12
  autoload :Error, 'rims/error'
14
13
  autoload :GDBM_KeyValueStore, 'rims/gdbm_kvs'
15
14
  autoload :GlobalDB, 'rims/db'
@@ -23,7 +22,6 @@ module RIMS
23
22
  autoload :MailboxDB, 'rims/db'
24
23
  autoload :MessageDB, 'rims/db'
25
24
  autoload :MessageSetSyntaxError, 'rims/protocol'
26
- autoload :Multiplexor, 'rims/server'
27
25
  autoload :ObjectPool, 'rims/pool'
28
26
  autoload :Password, 'rims/passwd'
29
27
  autoload :Protocol, 'rims/protocol'
@@ -32,8 +30,8 @@ module RIMS
32
30
  autoload :ReadLockError, 'rims/lock'
33
31
  autoload :ReadLockTimeoutError, 'rims/lock'
34
32
  autoload :ReadWriteLock, 'rims/lock'
35
- autoload :Server, 'rims/server'
36
33
  autoload :ServerResponseChannel, 'rims/channel'
34
+ autoload :Service, 'rims/service'
37
35
  autoload :SyntaxError, 'rims/protocol'
38
36
  autoload :Test, 'rims/test'
39
37
  autoload :WriteLockError, 'rims/lock'
@@ -40,6 +40,10 @@ module RIMS
40
40
  klass = PLUG_IN[name] or raise KeyError, "not found a password source plug-in: #{name}"
41
41
  klass.build_from_conf(config)
42
42
  end
43
+
44
+ def plug_in_names
45
+ PLUG_IN.keys
46
+ end
43
47
  end
44
48
 
45
49
  def initialize(hostname: 'rims',
@@ -49,8 +53,7 @@ module RIMS
49
53
  @time_source = time_source
50
54
  @random_string_source = random_string_source
51
55
  @capability = %w[ PLAIN CRAM-MD5 ]
52
- @plain_src = Password::PlainSource.new
53
- @passwd_src_list = [ @plain_src ]
56
+ @passwd_src_list = []
54
57
  end
55
58
 
56
59
  attr_reader :hostname
@@ -79,11 +82,6 @@ module RIMS
79
82
  end
80
83
  end
81
84
 
82
- def entry(username, password)
83
- @plain_src.entry(username, password)
84
- self
85
- end
86
-
87
85
  def user?(username)
88
86
  @passwd_src_list.any?{|passwd_src| passwd_src.user? username }
89
87
  end
@@ -1,13 +1,23 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ require 'json'
3
4
  require 'logger'
4
5
  require 'net/imap'
5
6
  require 'optparse'
6
7
  require 'pp'if $DEBUG
8
+ require 'riser'
7
9
  require 'syslog'
8
10
  require 'syslog/logger'
9
11
  require 'yaml'
10
12
 
13
+ OptionParser.accept(JSON) do |json_data, *_|
14
+ begin
15
+ JSON.load(json_data)
16
+ rescue
17
+ raise OptionParser::InvalidArgument, json_data
18
+ end
19
+ end
20
+
11
21
  module RIMS
12
22
  module Cmd
13
23
  CMDs = {}
@@ -46,20 +56,20 @@ module RIMS
46
56
  end
47
57
  options.parse!(args)
48
58
 
49
- STDERR.puts "usage: #{File.basename($0)} command options"
50
- STDERR.puts ""
51
- STDERR.puts "commands:"
59
+ puts "usage: #{File.basename($0)} command options"
60
+ puts ""
61
+ puts "commands:"
52
62
  w = CMDs.keys.map{|k| k.length }.max + 4
53
63
  fmt = " %- #{w}s%s"
54
64
  CMDs.each do |cmd_name, cmd_entry|
55
65
  if ((! show_debug_command) && (cmd_name =~ /^debug/)) then
56
66
  next
57
67
  end
58
- STDERR.puts format(fmt, cmd_name, cmd_entry[:description])
68
+ puts format(fmt, cmd_name, cmd_entry[:description])
59
69
  end
60
- STDERR.puts ""
61
- STDERR.puts "command help options:"
62
- STDERR.puts " -h, --help"
70
+ puts ""
71
+ puts "command help options:"
72
+ puts " -h, --help"
63
73
  0
64
74
  end
65
75
  command_function :cmd_help, "Show this message."
@@ -71,109 +81,592 @@ module RIMS
71
81
  end
72
82
  command_function :cmd_version, 'Show software version.'
73
83
 
74
- def make_server_config(options)
75
- conf = RIMS::Config.new
76
- conf.load(base_dir: Dir.getwd)
84
+ class ServiceConfigChainBuilder
85
+ def initialize
86
+ @build = proc{ Service::Configuration.new }
87
+ end
88
+
89
+ def chain(&block)
90
+ parent = @build
91
+ @build = proc{ block.call(parent.call) }
92
+ self
93
+ end
94
+
95
+ def call
96
+ @build.call
97
+ end
98
+ end
99
+
100
+ def make_service_config(options)
101
+ build = ServiceConfigChainBuilder.new
102
+ build.chain{|c| c.load(base_dir: Dir.getwd) }
103
+
104
+ options.summary_width = 37
105
+ log_level_list = %w[ debug info warn error fatal unknown ]
77
106
 
78
107
  options.on('-h', '--help', 'Show this message.') do
79
108
  puts options
80
109
  exit
81
110
  end
82
111
  options.on('-f', '--config-yaml=CONFIG_FILE',
83
- "Load optional parameters from CONFIG_FILE.") do |path|
84
- conf.load_config_yaml(path)
112
+ String,
113
+ "Load optional parameters from CONFIG_FILE."
114
+ ) do |path|
115
+ build.chain{|c| c.load_yaml(path) }
85
116
  end
86
- options.on('-d', '--base-dir=DIR',
87
- "Directory that places log file, mailbox database, etc. default is current directory.") do |path|
88
- conf.load(base_dir: path)
117
+ options.on('-r', '--required-feature=FEATURE',
118
+ String,
119
+ "Add required feature."
120
+ ) do |feature|
121
+ require(feature)
122
+ build.chain{|c| c.load(required_features: [ feature ]) }
89
123
  end
90
- level_list = %w[ debug info warn error fatal ]
91
- stdout_list = level_list + %w[ quiet ]
92
- options.on('-v', '--log-stdout=LEVEL', stdout_list,
93
- "Stdout logging level (#{stdout_list.join(' ')}). default is `info'.") do |level|
94
- conf.load(log_stdout: level)
124
+ options.on('-d', '--base-dir=DIR',
125
+ String,
126
+ "Directory that places log file, mailbox database, etc. default is current directory."
127
+ ) do |path|
128
+ build.chain{|c| c.load(base_dir: path) }
95
129
  end
96
130
  options.on('--log-file=FILE',
97
- "Name of log file. the directory part preceding file name is ignored. default is `imap.log'.") do |path|
98
- conf.load(log_file: path)
131
+ String,
132
+ "Name of log file. default is `#{Service::DEFAULT_CONFIG.make_file_logger_params[0]}'."
133
+ ) do |path|
134
+ build.chain{|c|
135
+ c.load(logger: {
136
+ file: {
137
+ path: path
138
+ }
139
+ })
140
+ }
141
+ end
142
+ options.on('-l', '--log-level=LEVEL',
143
+ log_level_list,
144
+ "Logging level (#{log_level_list.join(' ')}). default is `" +
145
+ Service::DEFAULT_CONFIG.make_file_logger_params[-1][:level] +
146
+ "'."
147
+ ) do |level|
148
+ build.chain{|c|
149
+ c.load(logging: {
150
+ file: {
151
+ level: level
152
+ }
153
+ })
154
+ }
155
+ end
156
+ options.on('--log-shift-age=NUMBER',
157
+ Integer,
158
+ 'Number of old log files to keep.'
159
+ ) do |num|
160
+ build.chain{|c|
161
+ c.load(logging: {
162
+ file: {
163
+ shift_age: num
164
+ }
165
+ })
166
+ }
99
167
  end
100
- options.on('-l', '--log-level=LEVEL', level_list,
101
- "Logging level (#{level_list.join(' ')}). default is `info'.") do |level|
102
- conf.load(log_level: level)
168
+ options.on('--log-shift-age-daily',
169
+ 'Frequency of daily log rotation.'
170
+ ) do
171
+ build.chain{|c|
172
+ c.load(logger: {
173
+ file: {
174
+ shift_age: 'daily'
175
+ }
176
+ })
177
+ }
103
178
  end
104
- options.on('--log-shift-age=NUMBER', Integer, 'Number of old log files to keep.') do |num|
105
- conf.load(log_shift_age: num)
179
+ options.on('--log-shift-age-weekly',
180
+ 'Frequency of weekly log rotation.'
181
+ ) do
182
+ build.chain{|c|
183
+ c.load(logger: {
184
+ file: {
185
+ shift_age: 'weekly'
186
+ }
187
+ })
188
+ }
189
+ end
190
+ options.on('--log-shift-age-monthly',
191
+ 'Frequency of monthly log rotation.'
192
+ ) do
193
+ build.chain{|c|
194
+ c.load(logger: {
195
+ file: {
196
+ shift_age: 'monthly'
197
+ }
198
+ })
199
+ }
106
200
  end
107
- options.on('--log-shift-age-daily', 'Frequency of daily log rotation.') do
108
- conf.load(log_shift_age: 'daily')
201
+ options.on('--log-shift-size=SIZE',
202
+ Integer,
203
+ 'Maximum logfile size.'
204
+ ) do |size|
205
+ build.chain{|c|
206
+ c.load(logger: {
207
+ file: {
208
+ shift_size: size
209
+ }
210
+ })
211
+ }
212
+ end
213
+ options.on('-v', '--log-stdout=LEVEL',
214
+ log_level_list + %w[ quiet ],
215
+ "Stdout logging level (#{(log_level_list + %w[ quiet ]).join(' ')}). default is `" +
216
+ Service::DEFAULT_CONFIG.make_stdout_logger_params[-1][:level] +
217
+ "'."
218
+ ) do |level|
219
+ if (level == 'quiet') then
220
+ level = 'unknown'
221
+ end
222
+ build.chain{|c|
223
+ c.load(logging: {
224
+ stdout: {
225
+ level: level
226
+ }
227
+ })
228
+ }
109
229
  end
110
- options.on('--log-shift-age-weekly', 'Frequency of weekly log rotation.') do
111
- conf.load(log_shift_age: 'weekly')
230
+ options.on('--protocol-log-file=FILE',
231
+ String,
232
+ "Name of log file. default is `#{Service::DEFAULT_CONFIG.make_protocol_logger_params[0]}'."
233
+ ) do |path|
234
+ build.chain{|c|
235
+ c.load(logger: {
236
+ protocol: {
237
+ path: path
238
+ }
239
+ })
240
+ }
112
241
  end
113
- options.on('--log-shift-age-monthly', 'Frequency of monthly log rotation.') do
114
- conf.load(log_shift_age: 'monthly')
242
+ options.on('-p', '--protocol-log-level=LEVEL',
243
+ log_level_list,
244
+ "Logging level (#{log_level_list.join(' ')}). default is `" +
245
+ Service::DEFAULT_CONFIG.make_protocol_logger_params[-1][:level] +
246
+ "'."
247
+ ) do |level|
248
+ build.chain{|c|
249
+ c.load(logging: {
250
+ protocol: {
251
+ level: level
252
+ }
253
+ })
254
+ }
115
255
  end
116
- options.on('--log-shift-size=SIZE', Integer, 'Maximum logfile size.') do |size|
117
- conf.load(log_shift_size: size)
256
+ options.on('--protocol-log-shift-age=NUMBER',
257
+ Integer,
258
+ 'Number of old log files to keep.'
259
+ ) do |num|
260
+ build.chain{|c|
261
+ c.load(logging: {
262
+ protocol: {
263
+ shift_age: num
264
+ }
265
+ })
266
+ }
118
267
  end
119
- options.on('--kvs-type=TYPE', %w[ gdbm ],
120
- "Choose the key-value store type of mailbox database. load plug-in on config.yml.") do |type|
121
- conf.load(key_value_store_type: type)
268
+ options.on('--protocol-log-shift-age-daily',
269
+ 'Frequency of daily log rotation.'
270
+ ) do
271
+ build.chain{|c|
272
+ c.load(logger: {
273
+ protocol: {
274
+ shift_age: 'daily'
275
+ }
276
+ })
277
+ }
122
278
  end
123
- options.on('--[no-]use-kvs-cksum',
124
- "Enable/disable data checksum at key-value store. default is enabled.") do |use|
125
- conf.load(use_key_value_store_checksum: use)
279
+ options.on('--protocol-log-shift-age-weekly',
280
+ 'Frequency of weekly log rotation.'
281
+ ) do
282
+ build.chain{|c|
283
+ c.load(logger: {
284
+ protocol: {
285
+ shift_age: 'weekly'
286
+ }
287
+ })
288
+ }
126
289
  end
127
- options.on('-u', '--username=NAME',
128
- "Username to login IMAP server. required parameter to start server.") do |name|
129
- conf.load(username: name)
290
+ options.on('--protocol-log-shift-age-monthly',
291
+ 'Frequency of monthly log rotation.'
292
+ ) do
293
+ build.chain{|c|
294
+ c.load(logger: {
295
+ protocol: {
296
+ shift_age: 'monthly'
297
+ }
298
+ })
299
+ }
130
300
  end
131
- options.on('-w', '--password=PASS',
132
- "Password to login IMAP server. required parameter to start server.") do |pass|
133
- conf.load(password: pass)
301
+ options.on('--protocol-log-shift-size=SIZE',
302
+ Integer,
303
+ 'Maximum logfile size.'
304
+ ) do |size|
305
+ build.chain{|c|
306
+ c.load(logger: {
307
+ protocol: {
308
+ shift_size: size
309
+ }
310
+ })
311
+ }
312
+ end
313
+ options.on('--[no-]daemonize',
314
+ "Daemonize server process. effective only with daemon command."
315
+ ) do |daemonize|
316
+ build.chain{|c|
317
+ c.load(daemon: {
318
+ daemonize: daemonize
319
+ })
320
+ }
321
+ end
322
+ options.on('--[no-]daemon-debug',
323
+ "Debug daemon. effective only with daemon command."
324
+ ) do |debug|
325
+ build.chain{|c|
326
+ c.load(daemon: {
327
+ debug: debug
328
+ })
329
+ }
330
+ end
331
+ options.on('--status-file=FILE',
332
+ String,
333
+ "Name of status file. effective only with daemon command. default is `#{Service::DEFAULT_CONFIG.status_file}'."
334
+ ) do |path|
335
+ build.chain{|c|
336
+ c.load(daemon: {
337
+ status_file: path
338
+ })
339
+ }
340
+ end
341
+ options.on('--privilege-user=USER',
342
+ String,
343
+ "Privilege user name or ID for server process. effective only with daemon command."
344
+ ) do |user|
345
+ build.chain{|c|
346
+ c.load(daemon: {
347
+ server_privileged_user: user
348
+ })
349
+ }
350
+ end
351
+ options.on('--privilege-group=GROUP',
352
+ String,
353
+ "Privilege group name or ID for server process. effective only with daemon command."
354
+ ) do |group|
355
+ build.chain{|c|
356
+ c.load(daemon: {
357
+ server_privileged_group: group
358
+ })
359
+ }
360
+ end
361
+ options.on('-s', '--listen=HOST_PORT',
362
+ String,
363
+ "Listen socket address. default is `#{Service::DEFAULT_CONFIG.listen_address}'"
364
+ ) do |host_port|
365
+ build.chain{|c|
366
+ c.load(server: {
367
+ listen_address: host_port
368
+ })
369
+ }
370
+ end
371
+ options.on('--accept-polling-timeout=SECONDS',
372
+ Float
373
+ ) do |seconds|
374
+ build.chain{|c|
375
+ c.load(server: {
376
+ accept_polling_timeout_seconds: seconds
377
+ })
378
+ }
379
+ end
380
+ options.on('--thread-num=NUMBER',
381
+ Integer
382
+ ) do |num|
383
+ build.chain{|c|
384
+ c.load(server: {
385
+ thread_num: num
386
+ })
387
+ }
388
+ end
389
+ options.on('--thread-queue-size=SIZE',
390
+ Integer
391
+ ) do |size|
392
+ build.chain{|c|
393
+ c.load(server: {
394
+ thread_queue_size: size
395
+ })
396
+ }
397
+ end
398
+ options.on('--thread-queue-polling-timeout=SECONDS',
399
+ Float
400
+ ) do |seconds|
401
+ build.chain{|c|
402
+ c.load(server: {
403
+ thread_queue_polling_timeout_seconds: seconds
404
+ })
405
+ }
406
+ end
407
+ options.on('--send-buffer-limit=SIZE',
408
+ Integer
409
+ ) do |size|
410
+ build.chain{|c|
411
+ c.load(server: {
412
+ send_buffer_limit_size: size
413
+ })
414
+ }
415
+ end
416
+ options.on('--read-lock-timeout=SECONDS',
417
+ Float
418
+ ) do |seconds|
419
+ build.chain{|c|
420
+ c.load(lock: {
421
+ read_lock_timeout_seconds: seconds
422
+ })
423
+ }
424
+ end
425
+ options.on('--write-lock-timeout=SECONDS',
426
+ Float
427
+ ) do |seconds|
428
+ build.chain{|c|
429
+ c.load(lock: {
430
+ write_lock_timeout_seconds: seconds
431
+ })
432
+ }
433
+ end
434
+ options.on('--cleanup-write-lock-timeout=SECONDS',
435
+ Float
436
+ ) do |seconds|
437
+ build.chain{|c|
438
+ c.load(lock: {
439
+ cleanup_write_lock_timeout_seconds: seconds
440
+ })
441
+ }
442
+ end
443
+ options.on('--meta-kvs-type=TYPE',
444
+ KeyValueStore::FactoryBuilder.plug_in_names,
445
+ "Choose key-value store type of mailbox meta-data database" +
446
+ if (KeyValueStore::FactoryBuilder.plug_in_names.length > 1) then
447
+ ' (' + KeyValueStore::FactoryBuilder.plug_in_names.join(' ') + ')'
448
+ else
449
+ ''
450
+ end +
451
+ ". default is `" +
452
+ KeyValueStore::FactoryBuilder.plug_in_names[0] +
453
+ "'."
454
+ ) do |kvs_type|
455
+ build.chain{|c|
456
+ c.load(storage: {
457
+ meta_key_value_store: {
458
+ type: kvs_type
459
+ }
460
+ })
461
+ }
462
+ end
463
+ options.on('--meta-kvs-config=JSON_DATA',
464
+ JSON,
465
+ "Configuration for key-value store of mailbox meta-data database."
466
+ ) do |json_data|
467
+ build.chain{|c|
468
+ c.load(storage: {
469
+ meta_key_value_store: {
470
+ configuration: json_data
471
+ }
472
+ })
473
+ }
474
+ end
475
+ options.on('--[no-]use-meta-kvs-checksum',
476
+ "Enable/disable data checksum at key-value store of mailbox meta-data database. default is " +
477
+ if (Service::DEFAULT_CONFIG.make_meta_key_value_store_params.middleware_list.include? Checksum_KeyValueStore) then
478
+ 'enabled'
479
+ else
480
+ 'disbled'
481
+ end +
482
+ "."
483
+ ) do |use_checksum|
484
+ build.chain{|c|
485
+ c.load(storage: {
486
+ meta_key_value_store: {
487
+ use_checksum: use_checksum
488
+ }
489
+ })
490
+ }
134
491
  end
492
+ options.on('--text-kvs-type=TYPE',
493
+ KeyValueStore::FactoryBuilder.plug_in_names,
494
+ "Choose key-value store type of mailbox text-data database" +
495
+ if (KeyValueStore::FactoryBuilder.plug_in_names.length > 1) then
496
+ ' (' + KeyValueStore::FactoryBuilder.plug_in_names.join(' ') + ')'
497
+ else
498
+ ''
499
+ end +
500
+ ". default is `" +
501
+ KeyValueStore::FactoryBuilder.plug_in_names[0] +
502
+ "'."
503
+ ) do |kvs_type|
504
+ build.chain{|c|
505
+ c.load(storage: {
506
+ text_key_value_store: {
507
+ type: kvs_type
508
+ }
509
+ })
510
+ }
511
+ end
512
+ options.on('--text-kvs-config=JSON_DATA',
513
+ JSON,
514
+ "Configuration for key-value store of mailbox text-data database."
515
+ ) do |json_data|
516
+ build.chain{|c|
517
+ c.load(storage: {
518
+ text_key_value_store: {
519
+ configuration: json_data
520
+ }
521
+ })
522
+ }
523
+ end
524
+ options.on('--[no-]use-text-kvs-checksum',
525
+ "Enable/disable data checksum at key-value store of mailbox text-data database. default is " +
526
+ if (Service::DEFAULT_CONFIG.make_text_key_value_store_params.middleware_list.include? Checksum_KeyValueStore) then
527
+ 'enabled'
528
+ else
529
+ 'disbled'
530
+ end +
531
+ "."
532
+ ) do |use_checksum|
533
+ build.chain{|c|
534
+ c.load(storage: {
535
+ text_key_value_store: {
536
+ use_checksum: use_checksum
537
+ }
538
+ })
539
+ }
540
+ end
541
+ options.on('--auth-hostname=HOSTNAME',
542
+ String,
543
+ "Hostname to authenticate with cram-md5. default is `#{Service::DEFAULT_CONFIG.make_authentication.hostname}'."
544
+ ) do |hostname|
545
+ build.chain{|c|
546
+ c.load(authentication: {
547
+ hostname: hostname
548
+ })
549
+ }
550
+ end
551
+ options.on('--passwd-config=TYPE_JSONDATA',
552
+ /([^:]+)(?::(.*))?/,
553
+ "Password source type (#{Authentication.plug_in_names.join(',')}) and configuration. format is `[type]:[json_data]'."
554
+ ) do |_, type, json_data|
555
+ build.chain{|c|
556
+ c.load(authentication: {
557
+ password_sources: [
558
+ { type: type,
559
+ configuration: JSON.load(json_data)
560
+ }
561
+ ]
562
+ })
563
+ }
564
+ end
565
+ options.on('--passwd-file=TYPE_FILE',
566
+ /([^:]+):(.+)/,
567
+ "Password source type (#{Authentication.plug_in_names.join(',')}) and configuration file. format is `[type]:[file]'."
568
+ ) do |_, type, path|
569
+ build.chain{|c|
570
+ c.load(authentication: {
571
+ password_sources: [
572
+ { type: type,
573
+ configuration_file: path
574
+ }
575
+ ]
576
+ })
577
+ }
578
+ end
579
+ options.on('--mail-delivery-user=USERNAME',
580
+ String,
581
+ "Username authorized to deliver messages to any mailbox. default is `#{Service::DEFAULT_CONFIG.mail_delivery_user}'"
582
+ ) do |username|
583
+ build.chain{|c|
584
+ c.load(authorization: {
585
+ mail_delivery_user: username
586
+ })
587
+ }
588
+ end
589
+
135
590
  options.on('--imap-host=HOSTNAME',
136
- "IMAP server hostname or IP address for the server to bind. default is `#{Server::DEFAULT[:imap_host]}'.") do |host|
137
- conf.load(imap_host: host)
591
+ String,
592
+ 'Deplicated.'
593
+ ) do |host|
594
+ warn("warning: `--imap-host=HOSTNAME' is deplicated option and should use `--listen=HOST_PORT'.")
595
+ build.chain{|c| c.load(imap_host: host) }
138
596
  end
139
597
  options.on('--imap-port=PORT',
140
- "IMAP server port number or service name for the server to bind. default is `#{Server::DEFAULT[:imap_port]}'.") do |value|
141
- if (value =~ /\A\d+\z/) then
598
+ String,
599
+ 'Deplicated.'
600
+ ) do |value|
601
+ warn("warning: `--imap-port=PORT' is deplicated option and should use `--listen=HOST_PORT'.")
602
+ if (value =~ /\A \d+ \z/x) then
142
603
  port_number = value.to_i
143
- conf.load(imap_port: port_number)
604
+ build.chain{|c| c.load(imap_port: port_number) }
144
605
  else
145
606
  service_name = value
146
- conf.load(imap_port: service_name)
607
+ build.chain{|c| c.load(imap_port: service_name) }
147
608
  end
148
609
  end
149
- options.on('--privilege-user=NAME',
150
- "Privilege user name or ID for server process. default is #{Server::DEFAULT[:process_privilege_uid]}.") do |name|
151
- conf.load(process_privilege_user: name)
610
+ options.on('--ip-addr=IP_ADDR',
611
+ String,
612
+ 'Deplicated.'
613
+ ) do |ip_addr|
614
+ warn("warning: `--ip-addr=IP_ADDR' is deplicated option and should use `--listen=HOST_PORT'.")
615
+ build.chain{|c| c.load(ip_addr: ip_addr) }
152
616
  end
153
- options.on('--privilege-group=NAME',
154
- "Privilege group name or ID for server process. default is #{Server::DEFAULT[:process_privilege_gid]}.") do |name|
155
- conf.load(process_privilege_user: name)
617
+ options.on('--ip-port=PORT',
618
+ Integer,
619
+ 'Deplicated.'
620
+ ) do |port|
621
+ warn("warning: `--ip-port=PORT' is deplicated option and should use `--listen=HOST_PORT'.")
622
+ build.chain{|c| c.load(ip_port: port) }
156
623
  end
157
-
158
- options.on('--ip-addr=IP_ADDR', 'obsoleted.') do |ip_addr|
159
- warn("warning: `--ip-addr=IP_ADDR' is obsoleted option and should use `--imap-host=HOSTNAME'.")
160
- conf.load(ip_addr: ip_addr)
624
+ options.on('--kvs-type=TYPE',
625
+ KeyValueStore::FactoryBuilder.plug_in_names,
626
+ 'Deplicated.'
627
+ ) do |kvs_type|
628
+ warn("warning: `--kvs-type=TYPE' is deplicated option and should use `--meta-kvs-type=TYPE' or `--text-kvs-type=TYPE'.")
629
+ build.chain{|c| c.load(key_value_store_type: kvs_type) }
161
630
  end
162
- options.on('--ip-port=PORT', Integer, 'obsoleted.') do |port|
163
- warn("warning: `--ip-port=PORT' is obsoleted option and should use `--imap-port=PORT'.")
164
- conf.load(ip_port: port)
631
+ options.on('--[no-]use-kvs-cksum',
632
+ 'Deplicated.'
633
+ ) do |use_checksum|
634
+ warn("warning: `--[no-]use-kvs-cksum' is deplicated option and should use `--[no-]use-meta-kvs-checksum' or `--[no-]use-text-kvs-checksum'.")
635
+ build.chain{|c| c.load(use_key_value_store_checksum: use_checksum) }
636
+ end
637
+ options.on('-u', '--username=NAME',
638
+ String,
639
+ 'Deplicated.'
640
+ ) do |name|
641
+ warn("warning: `--username=NAME' is deplicated option and should use `--passwd-config=TYPE_JSONDATA' or `--passwd-file=TYPE_FILE'.")
642
+ build.chain{|c| c.load(username: name) }
643
+ end
644
+ options.on('-w', '--password=PASS',
645
+ String,
646
+ 'Deplicated.'
647
+ ) do |pass|
648
+ warn("warning: `--password=PASS' is deplicated option and should use `--passwd-config=TYPE_JSONDATA' or `--passwd-file=TYPE_FILE'.")
649
+ build.chain{|c| c.load(password: pass) }
165
650
  end
166
651
 
167
- conf
652
+ build
168
653
  end
169
- module_function :make_server_config
654
+ module_function :make_service_config
170
655
 
171
656
  def cmd_server(options, args)
172
- conf = make_server_config(options)
657
+ build = make_service_config(options)
173
658
  options.parse!(args)
174
659
 
175
- server = conf.build_server
176
- server.start
660
+ config = build.call
661
+ server = Riser::SocketServer.new
662
+ service = RIMS::Service.new(config)
663
+ service.setup(server)
664
+
665
+ Signal.trap(:INT) { server.signal_stop_forced }
666
+ Signal.trap(:TERM) { server.signal_stop_graceful }
667
+
668
+ listen_address = Riser::SocketAddress.parse(config.listen_address)
669
+ server.start(listen_address.open_server)
177
670
 
178
671
  0
179
672
  end
@@ -190,7 +683,7 @@ module RIMS
190
683
  end
191
684
  private :imap_res2str
192
685
 
193
- IMAP_AUTH_TYPE_LIST = %w[ login plain cram-md5 ]
686
+ IMAP_AUTH_TYPE_LIST = %w[ login plain cram-md5 ]
194
687
  MAIL_DATE_PLACE_LIST = [ :servertime, :localtime, :filetime, :mailheader ]
195
688
 
196
689
  VERBOSE_OPTION_LIST = [
@@ -198,35 +691,33 @@ module RIMS
198
691
  ]
199
692
 
200
693
  def self.make_imap_connect_option_list(imap_host: 'localhost', imap_port: 143, imap_ssl: false, auth_type: 'login', username: nil)
201
- [ [ :imap_host, imap_host, '-n', '--host=HOSTNAME', "Hostname or IP address to connect IMAP server. default is `#{imap_host}'." ],
202
- [ :imap_port, imap_port, '-o', '--port=PORT', Integer, "Server port number or service name to connect IMAP server. default is #{imap_port}." ],
203
- [ :imap_ssl, imap_ssl, '-s', '--[no-]use-ssl', "Enable SSL/TLS connection. default is #{imap_ssl ? 'enabled' : 'disabled'}." ],
204
- [ :username, username, '-u', '--username=NAME',
205
- "Username to login IMAP server. " + if (username) then
206
- "default is `#{username}'."
207
- else
208
- "required parameter to connect server."
209
- end ],
210
- [ :password, nil, '-w', '--password=PASS', "Password to login IMAP server. required parameter to connect server." ],
211
- [ :auth_type, auth_type, '--auth-type=METHOD', IMAP_AUTH_TYPE_LIST,
212
- "Choose authentication method type (#{IMAP_AUTH_TYPE_LIST.join(' ')}). default is `#{auth_type}'." ]
694
+ [ [ :imap_host, imap_host, '-n', '--host=HOSTNAME', "Hostname or IP address to connect IMAP server. default is `#{imap_host}'." ],
695
+ [ :imap_port, imap_port, '-o', '--port=PORT', Integer, "Server port number or service name to connect IMAP server. default is #{imap_port}." ],
696
+ [ :imap_ssl, imap_ssl, '-s', '--[no-]use-ssl', "Enable SSL/TLS connection. default is #{imap_ssl ? 'enabled' : 'disabled'}." ],
697
+ [ :ca_cert, nil, '--ca-cert=PATH', "CA cert file or directory." ],
698
+ [ :ssl_params, {}, '--ssl-params=JSON_DATA', JSON, "SSLContext#set_params as parameters." ],
699
+ [ :username, username, '-u', '--username=NAME', "Username to login IMAP server. " +
700
+ (username ? "default is `#{username}'." : "required parameter to connect server.") ],
701
+ [ :password, nil, '-w', '--password=PASS', "Password to login IMAP server. required parameter to connect server." ],
702
+ [ :auth_type, auth_type, '--auth-type=METHOD', IMAP_AUTH_TYPE_LIST, "Choose authentication method type (#{IMAP_AUTH_TYPE_LIST.join(' ')}). " +
703
+ "default is `#{auth_type}'." ]
213
704
  ]
214
705
  end
215
706
 
216
- IMAP_CONNECT_OPTION_LIST = self.make_imap_connect_option_list
217
- POST_MAIL_CONNECT_OPTION_LIST = self.make_imap_connect_option_list(imap_port: Server::DEFAULT[:imap_port],
218
- username: Server::DEFAULT[:mail_delivery_user])
707
+ IMAP_CONNECT_OPTION_LIST = make_imap_connect_option_list
708
+ POST_MAIL_CONNECT_OPTION_LIST = make_imap_connect_option_list(imap_port: Riser::SocketAddress.parse(Service::DEFAULT_CONFIG.listen_address).port,
709
+ username: Service::DEFAULT_CONFIG.mail_delivery_user)
219
710
 
220
711
  IMAP_MAILBOX_OPTION_LIST = [
221
- [ :mailbox, 'INBOX', '-m', '--mailbox=NAME', "Set mailbox name to append messages. default is `INBOX'." ]
712
+ [ :mailbox, 'INBOX', '-m', '--mailbox=NAME', String, "Set mailbox name to append messages. default is `INBOX'." ]
222
713
  ]
223
714
 
224
715
  IMAP_STORE_FLAG_OPTION_LIST = [
225
716
  [ :store_flag_answered, false, '--[no-]store-flag-answered', "Store answered flag on appending messages to mailbox. default is no flag." ],
226
- [ :store_flag_flagged, false, '--[no-]store-flag-flagged', "Store flagged flag on appending messages to mailbox. default is no flag." ],
227
- [ :store_flag_deleted, false, '--[no-]store-flag-deleted', "Store deleted flag on appending messages to mailbox. default is no flag." ],
228
- [ :store_flag_seen, false, '--[no-]store-flag-seen', "Store seen flag on appending messages to mailbox. default is no flag." ],
229
- [ :store_flag_draft, false, '--[no-]store-flag-draft', "Store draft flag on appending messages to mailbox. default is no flag." ]
717
+ [ :store_flag_flagged, false, '--[no-]store-flag-flagged', "Store flagged flag on appending messages to mailbox. default is no flag." ],
718
+ [ :store_flag_deleted, false, '--[no-]store-flag-deleted', "Store deleted flag on appending messages to mailbox. default is no flag." ],
719
+ [ :store_flag_seen, false, '--[no-]store-flag-seen', "Store seen flag on appending messages to mailbox. default is no flag." ],
720
+ [ :store_flag_draft, false, '--[no-]store-flag-draft', "Store draft flag on appending messages to mailbox. default is no flag." ]
230
721
  ]
231
722
 
232
723
  MAIL_DATE_OPTION_LIST = [
@@ -235,11 +726,43 @@ module RIMS
235
726
  ]
236
727
  ]
237
728
 
729
+ def self.symbolize_string_key(collection)
730
+ case (collection)
731
+ when Hash
732
+ Hash[collection.map{|key, value|
733
+ [ symbolize_string_key(key),
734
+ case (value)
735
+ when Hash, Array
736
+ symbolize_string_key(value)
737
+ else
738
+ value
739
+ end
740
+ ]
741
+ }]
742
+ when Array
743
+ collection.map{|value|
744
+ case (value)
745
+ when Hash, Array
746
+ symbolize_string_key(value)
747
+ else
748
+ value
749
+ end
750
+ }
751
+ else
752
+ case (value = collection)
753
+ when String
754
+ value.to_sym
755
+ else
756
+ value
757
+ end
758
+ end
759
+ end
760
+
238
761
  def initialize(options, option_list)
239
762
  @options = options
240
763
  @option_list = option_list
241
764
  @conf = {}
242
- for key, value, *option_description in option_list
765
+ for key, value, *_option_description in option_list
243
766
  @conf[key] = value
244
767
  end
245
768
  end
@@ -282,17 +805,22 @@ module RIMS
282
805
 
283
806
  def load_config_option
284
807
  @options.on('-f', '--config-yaml=CONFIG_FILE',
808
+ String,
285
809
  "Load optional parameters from CONFIG_FILE.") do |path|
286
- for name, value in YAML.load_file(path)
287
- @conf[name.to_sym] = value
288
- end
810
+ config = YAML.load_file(path)
811
+ symbolized_config = self.class.symbolize_string_key(config)
812
+ @conf.update(symbolized_config)
289
813
  end
290
814
 
291
815
  self
292
816
  end
293
817
 
294
- def load_library_option
295
- @options.on('-r', '--load-library=LIBRARY', 'require LIBRARY.') do |library|
818
+ def required_feature_option
819
+ @options.on('-r', '--required-feature=FEATURE', String, 'Add required feature.') do |feature|
820
+ require(feature)
821
+ end
822
+ @options.on('--load-library=LIBRARY', String, 'Deplicated.') do |library|
823
+ warn("warning: `--load-library=LIBRARY' is deplicated option and should use `--required-feature=FEATURE'.")
296
824
  require(library)
297
825
  end
298
826
 
@@ -301,12 +829,27 @@ module RIMS
301
829
 
302
830
  def key_value_store_option
303
831
  @conf[:key_value_store_type] = GDBM_KeyValueStore
304
- @options.on('--kvs-type=TYPE', 'Choose the key-value store type.') do |kvs_type|
832
+ @options.on('--kvs-type=TYPE',
833
+ KeyValueStore::FactoryBuilder.plug_in_names,
834
+ "Choose key-value store type of mailbox database" +
835
+ if (KeyValueStore::FactoryBuilder.plug_in_names.length > 1) then
836
+ ' (' + KeyValueStore::FactoryBuilder.plug_in_names.join(' ') + ')'
837
+ else
838
+ ''
839
+ end +
840
+ ". default is `" +
841
+ KeyValueStore::FactoryBuilder.plug_in_names[0] +
842
+ "'."
843
+ ) do |kvs_type|
305
844
  @conf[:key_value_store_type] = KeyValueStore::FactoryBuilder.get_plug_in(kvs_type)
306
845
  end
307
846
 
308
847
  @conf[:use_key_value_store_checksum] = true
309
- @options.on('--[no-]use-kvs-cksum', 'Enable/disable data checksum at key-value store. default is enabled.') do |use_checksum|
848
+ @options.on('--[no-]use-kvs-checksum', 'Enable/disable data checksum at key-value store. default is enabled.') do |use_checksum|
849
+ @conf[:use_key_value_store_checksum] = use_checksum
850
+ end
851
+ @options.on('--[no-]use-kvs-cksum', 'Deplicated.') do |use_checksum|
852
+ warn("warning: `--[no-]use-kvs-cksum' is deplicated option and should use `--[no-]use-kvs-checksum'.")
310
853
  @conf[:use_key_value_store_checksum] = use_checksum
311
854
  end
312
855
 
@@ -338,7 +881,24 @@ module RIMS
338
881
  raise 'need for username and password.'
339
882
  end
340
883
 
341
- imap = Net::IMAP.new(@conf[:imap_host], port: @conf[:imap_port], ssl: @conf[:imap_ssl])
884
+ args = [ @conf[:imap_host] ]
885
+ if (@conf[:imap_ssl]) then
886
+ if (@conf[:ssl_params].empty?) then
887
+ args << @conf[:imap_port]
888
+ args << @conf[:imap_ssl]
889
+ args << @conf[:ca_cert]
890
+ else
891
+ kw_args = {
892
+ port: @conf[:imap_port],
893
+ ssl: @conf[:ssl_params]
894
+ }
895
+ args << kw_args
896
+ end
897
+ else
898
+ args << @conf[:imap_port]
899
+ end
900
+
901
+ imap = Net::IMAP.new(*args)
342
902
  begin
343
903
  if (@conf[:verbose]) then
344
904
  puts "server greeting: #{imap_res2str(imap.greeting)}"
@@ -358,7 +918,7 @@ module RIMS
358
918
 
359
919
  yield(imap)
360
920
  ensure
361
- Error.suppress_2nd_error_at_resource_closing{ imap.logout }
921
+ imap.logout
362
922
  end
363
923
  end
364
924
 
@@ -408,15 +968,20 @@ module RIMS
408
968
 
409
969
  def cmd_daemon(options, args)
410
970
  conf = Config.new(options,
411
- [ [ :is_daemon,
971
+ [ [ :use_status_code,
412
972
  true,
973
+ '--[no-]status-code',
974
+ "Return the result of `status' operation as an exit code."
975
+ ],
976
+ [ :is_daemon,
977
+ nil,
413
978
  '--[no-]daemon',
414
- 'Start daemon process. default is enabled.'
979
+ 'Obsoleted.'
415
980
  ],
416
981
  [ :is_syslog,
417
- true,
982
+ nil,
418
983
  '--[no-]syslog',
419
- 'Syslog daemon messages. default is enabled.'
984
+ 'Obsoleted.'
420
985
  ]
421
986
  ])
422
987
  conf.help_option(add_banner: ' start/stop/restart/status [server options]')
@@ -426,73 +991,75 @@ module RIMS
426
991
  pp args if $DEBUG
427
992
 
428
993
  operation = args.shift or raise 'need for daemon operation.'
429
- server_args = args.dup
430
994
  server_options = OptionParser.new
431
- server_conf = make_server_config(server_options)
432
- server_options.parse!(server_args)
433
- stat_file_path = Daemon.make_stat_file_path(server_conf.base_dir)
434
- pp server_conf if $DEBUG
435
-
436
- case (operation)
437
- when 'start'
438
- if (conf[:is_daemon]) then
439
- args += %w[ --log-stdout=quiet ]
440
- Process.daemon(true)
441
- end
995
+ build = make_service_config(server_options)
996
+ server_options.parse!(args)
442
997
 
443
- logger = Multiplexor.new
444
- unless (conf[:is_daemon]) then
445
- stdout_logger = Logger.new(STDOUT)
446
- def stdout_logger.close # should not be closed at child process.
447
- nil
448
- end
449
- logger.add(stdout_logger)
450
- end
451
- if (conf[:is_syslog]) then
452
- syslog_logger = Syslog::Logger.new('rims-daemon')
453
- def syslog_logger.close # should be closed at child process.
454
- Syslog.close
455
- end
456
- logger.add(syslog_logger)
457
- end
998
+ unless (conf[:is_daemon].nil?) then
999
+ warn("warning: `--[no-]daemon' is obsoleted option and no effect. use server option `--[no-]daemonize'.")
1000
+ end
1001
+ unless (conf[:is_syslog].nil?) then
1002
+ warn("warning: `--[no-]syslog' is obsoleted option and no effect.")
1003
+ end
458
1004
 
459
- daemon = Daemon.new(stat_file_path, logger, server_options: args)
1005
+ svc_conf = build.call
1006
+ pp svc_conf if $DEBUG
460
1007
 
461
- [ [ Daemon::RELOAD_SIGNAL_LIST, proc{ daemon.reload_server } ],
462
- [ Daemon::RESTART_SIGNAL_LIST, proc{ daemon.restart_server } ],
463
- [ Daemon::STOP_SIGNAL_LIST, proc{ daemon.stop_server } ]
464
- ].each do |signal_list, signal_command|
465
- for sig_name in signal_list
466
- Signal.trap(sig_name, signal_command)
467
- end
1008
+ status_file_locked = lambda{
1009
+ begin
1010
+ File.open(svc_conf.status_file, File::WRONLY) {|lock_file|
1011
+ ! lock_file.flock(File::LOCK_EX | File::LOCK_NB)
1012
+ }
1013
+ rescue Errno::ENOENT
1014
+ false
468
1015
  end
1016
+ }
469
1017
 
470
- daemon.run
471
- when 'stop'
472
- stat_file = Daemon.new_status_file(stat_file_path)
473
- stat_file.open{
474
- stat_file.should_be_locked
475
- pid = YAML.load(stat_file.read)['pid']
476
- Process.kill(Daemon::STOP_SIGNAL, pid)
1018
+ start_daemon = lambda{
1019
+ Riser::Daemon.start_daemon(daemonize: svc_conf.daemonize?,
1020
+ daemon_name: svc_conf.daemon_name,
1021
+ daemon_debug: svc_conf.daemon_debug?,
1022
+ status_file: svc_conf.status_file,
1023
+ listen_address: proc{
1024
+ # to reload on server restart
1025
+ build.call.listen_address
1026
+ },
1027
+ server_polling_interval_seconds: svc_conf.server_polling_interval_seconds,
1028
+ server_restart_overlap_seconds: svc_conf.server_restart_overlap_seconds,
1029
+ server_privileged_user: svc_conf.server_privileged_user,
1030
+ server_privileged_group: svc_conf.server_privileged_group
1031
+ ) {|server|
1032
+ c = build.call # to reload on server restart
1033
+ service = RIMS::Service.new(c)
1034
+ service.setup(server, daemon: true)
477
1035
  }
1036
+ }
1037
+
1038
+ case (operation)
1039
+ when 'start'
1040
+ start_daemon.call
1041
+ when 'stop'
1042
+ if (status_file_locked.call) then
1043
+ pid = YAML.load(IO.read(svc_conf.status_file))['pid']
1044
+ Process.kill(Riser::Daemon::SIGNAL_STOP_GRACEFUL, pid)
1045
+ else
1046
+ abort('No daemon.')
1047
+ end
478
1048
  when 'restart'
479
- stat_file = Daemon.new_status_file(stat_file_path)
480
- stat_file.open{
481
- stat_file.should_be_locked
482
- pid = YAML.load(stat_file.read)['pid']
483
- Process.kill(Daemon::RESTART_SIGNAL, pid)
484
- }
1049
+ if (status_file_locked.call) then
1050
+ pid = YAML.load(IO.read(svc_conf.status_file))['pid']
1051
+ Process.kill(Riser::Daemon::SIGNAL_RESTART_GRACEFUL, pid)
1052
+ else
1053
+ start_daemon.call
1054
+ end
485
1055
  when 'status'
486
- stat_file = Daemon.new_status_file(stat_file_path)
487
- stat_file.open{
488
- if (stat_file.locked?) then
489
- puts 'daemon is running.' if conf[:verbose]
490
- return 0
491
- else
492
- puts 'daemon is stopped.' if conf[:verbose]
493
- return 1
494
- end
495
- }
1056
+ if (status_file_locked.call) then
1057
+ puts 'daemon is running.' if conf[:verbose]
1058
+ return 0 if conf[:use_status_code]
1059
+ else
1060
+ puts 'daemon is stopped.' if conf[:verbose]
1061
+ return 1 if conf[:use_status_code]
1062
+ end
496
1063
  else
497
1064
  raise "unknown daemon operation: #{operation}"
498
1065
  end
@@ -513,7 +1080,7 @@ module RIMS
513
1080
  def each_message(args, verbose: false)
514
1081
  if (args.empty?) then
515
1082
  msg_txt = STDIN.read
516
- yield(msg_txt)
1083
+ yield(msg_txt, nil)
517
1084
  return 0
518
1085
  else
519
1086
  error_count = 0
@@ -521,14 +1088,16 @@ module RIMS
521
1088
  puts "progress: #{i + 1}/#{args.length}" if verbose
522
1089
  begin
523
1090
  msg_txt = IO.read(filename, mode: 'rb', encoding: 'ascii-8bit')
524
- yield(msg_txt)
1091
+ yield(msg_txt, filename)
525
1092
  rescue
526
1093
  error_count += 1
527
1094
  puts "failed to append message: #{filename}"
528
- puts "error: #{$!}"
529
- if ($DEBUG) then
530
- for frame in $!.backtrace
531
- puts frame
1095
+ Error.trace_error_chain($!) do |exception|
1096
+ puts "error: #{exception}"
1097
+ if ($DEBUG) then
1098
+ for frame in exception.backtrace
1099
+ puts frame
1100
+ end
532
1101
  end
533
1102
  end
534
1103
  end
@@ -568,8 +1137,8 @@ module RIMS
568
1137
  unless (imap.capability.find{|c| c == 'X-RIMS-MAIL-DELIVERY-USER' }) then
569
1138
  warn('warning: This IMAP server might not support RIMS mail delivery protocol.')
570
1139
  end
571
- each_message(args) do |msg_txt|
572
- t = conf.look_for_date(msg_txt)
1140
+ each_message(args) do |msg_txt, filename|
1141
+ t = conf.look_for_date(msg_txt, filename)
573
1142
  encoded_mbox_name = Protocol::Decoder.encode_delivery_target_mailbox(post_user, conf[:mailbox])
574
1143
  imap_append(imap, encoded_mbox_name, msg_txt, store_flags: store_flags, date_time: t, verbose: conf[:verbose])
575
1144
  end
@@ -596,8 +1165,8 @@ module RIMS
596
1165
 
597
1166
  store_flags = conf.make_imap_store_flags
598
1167
  conf.imap_connect{|imap|
599
- each_message(args) do |msg_txt|
600
- t = conf.look_for_date(msg_txt)
1168
+ each_message(args) do |msg_txt, filename|
1169
+ t = conf.look_for_date(msg_txt, filename)
601
1170
  imap_append(imap, conf[:mailbox], msg_txt, store_flags: store_flags, date_time: t, verbose: conf[:verbose])
602
1171
  end
603
1172
  }
@@ -610,7 +1179,7 @@ module RIMS
610
1179
  ]
611
1180
 
612
1181
  conf = Config.new(options, option_list)
613
- conf.load_library_option
1182
+ conf.required_feature_option
614
1183
  conf.key_value_store_option
615
1184
  conf.help_option(add_banner: ' [mailbox directory]')
616
1185
  conf.quiet_option
@@ -650,7 +1219,7 @@ module RIMS
650
1219
  0
651
1220
  end
652
1221
  ensure
653
- Error.suppress_2nd_error_at_resource_closing{ meta_db.close }
1222
+ meta_db.close
654
1223
  end
655
1224
  end
656
1225
  command_function :cmd_mbox_dirty_flag, 'Show/enable/disable dirty flag of mailbox database.'
@@ -671,25 +1240,26 @@ module RIMS
671
1240
  command_function :cmd_unique_user_id, 'Show unique user ID from username.'
672
1241
 
673
1242
  def cmd_show_user_mbox(options, args)
674
- conf = RIMS::Config.new
675
- load_server_config = false
1243
+ svc_conf = RIMS::Service::Configuration.new
1244
+ load_service_config = false
676
1245
 
677
1246
  options.banner += ' [base directory] [username] OR -f [config.yml path] [username]'
678
1247
  options.on('-f', '--config-yaml=CONFIG_FILE',
1248
+ String,
679
1249
  'Load optional parameters from CONFIG_FILE.') do |path|
680
- conf.load_config_yaml(path)
681
- load_server_config = true
1250
+ svc_conf.load_yaml(path)
1251
+ load_service_config = true
682
1252
  end
683
1253
  options.parse!(args)
684
1254
 
685
- unless (load_server_config) then
1255
+ unless (load_service_config) then
686
1256
  base_dir = args.shift or raise 'need for base directory.'
687
- conf.load(base_dir: base_dir)
1257
+ svc_conf.load(base_dir: base_dir)
688
1258
  end
689
1259
 
690
1260
  username = args.shift or raise 'need for a username.'
691
1261
  unique_user_id = Authentication.unique_user_id(username)
692
- puts conf.make_key_value_store_path_from_base_dir(MAILBOX_DATA_STRUCTURE_VERSION, unique_user_id)
1262
+ puts svc_conf.make_key_value_store_path(MAILBOX_DATA_STRUCTURE_VERSION, unique_user_id)
693
1263
 
694
1264
  0
695
1265
  end
@@ -697,7 +1267,7 @@ module RIMS
697
1267
 
698
1268
  def cmd_pass_hash(options, args)
699
1269
  option_list = [
700
- [ :hash_type, 'SHA256', '--hash-type=DIGEST', 'Password hash type (ex SHA256, MD5, etc). default is SHA256.' ],
1270
+ [ :hash_type, 'SHA256', '--hash-type=DIGEST', String, 'Password hash type (ex SHA256, MD5, etc). default is SHA256.' ],
701
1271
  [ :stretch_count, 10000, '--stretch-count=COUNT', Integer, 'Count to stretch password hash. default is 10000.' ],
702
1272
  [ :salt_size, 16, '--salt-size=OCTETS', Integer, 'Size of salt string. default is 16 octets.' ]
703
1273
  ]
@@ -724,9 +1294,9 @@ Options:
724
1294
 
725
1295
  case (args.length)
726
1296
  when 0
727
- passwd, *optional = YAML.load_stream(STDIN)
1297
+ passwd, *_optional = YAML.load_stream(STDIN)
728
1298
  when 1
729
- passwd, *optional = File.open(args[0]) {|f| YAML.load_stream(f) }
1299
+ passwd, *_optional = File.open(args[0]) {|f| YAML.load_stream(f) }
730
1300
  else
731
1301
  raise ArgumentError, 'too many input files.'
732
1302
  end
@@ -754,7 +1324,7 @@ Options:
754
1324
  ]
755
1325
 
756
1326
  conf = Config.new(options, option_list)
757
- conf.load_library_option
1327
+ conf.required_feature_option
758
1328
  conf.key_value_store_option
759
1329
  conf.help_option(add_banner: ' [DB_NAME]')
760
1330
  conf.setup_option_list
@@ -794,7 +1364,7 @@ Options:
794
1364
  puts entry
795
1365
  end
796
1366
  ensure
797
- Error.suppress_2nd_error_at_resource_closing{ db.close }
1367
+ db.close
798
1368
  end
799
1369
 
800
1370
  0