rims 0.2.2 → 0.2.3

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