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 +4 -4
- data/.gitignore +19 -17
- data/ChangeLog +36 -0
- data/Rakefile +70 -1
- data/lib/rims.rb +3 -5
- data/lib/rims/auth.rb +5 -7
- data/lib/rims/cmd.rb +758 -188
- data/lib/rims/error.rb +8 -10
- data/lib/rims/kvs.rb +4 -0
- data/lib/rims/lock.rb +4 -1
- data/lib/rims/protocol.rb +7 -1
- data/lib/rims/protocol/decoder.rb +24 -7
- data/lib/rims/protocol/parser.rb +1 -1
- data/lib/rims/service.rb +953 -0
- data/lib/rims/version.rb +1 -1
- data/rims.gemspec +2 -0
- data/test/cmd/test_command.rb +999 -0
- data/test/test_auth.rb +3 -1
- data/test/test_cmd.rb +36 -0
- data/test/test_error.rb +22 -78
- data/test/test_protocol_auth.rb +3 -1
- data/test/test_protocol_decoder.rb +6 -3
- data/test/test_service.rb +1120 -0
- metadata +38 -11
- data/lib/rims/daemon.rb +0 -338
- data/lib/rims/server.rb +0 -567
- data/test/test_config.rb +0 -533
- data/test/test_daemon_status_file.rb +0 -169
- data/test/test_daemon_waitpid.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 397075dab0a3fa9bdf66a3a3be278b2d1eb3f67f6e2b12e73dcd27bf37190e66
|
4
|
+
data.tar.gz: 8e0734fcdda1d88e9720cefea0c8a2464f4fd4ddb5cfd68c67bc56635c9ef6f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b9fa09f69d35c243e8be71387fe29c4ab3b2237e2a530737e5108dba9371579c2fda3d9fa6bfa7bbebb0ffef3121e6e84863fa111c31b93d1ef8aeeb979c870
|
7
|
+
data.tar.gz: 8cb95a095d7f15f0fe6c7a7ac3bb6d4ec845e6950923c50dc69a93428c082576faaf0a48d83273a923d923dafc6a85eced6affb2c5039408f9b83add0cddd6e9
|
data/.gitignore
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
data/lib/rims.rb
CHANGED
@@ -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'
|
data/lib/rims/auth.rb
CHANGED
@@ -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
|
-
@
|
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
|
data/lib/rims/cmd.rb
CHANGED
@@ -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
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
68
|
+
puts format(fmt, cmd_name, cmd_entry[:description])
|
59
69
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
84
|
-
|
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('-
|
87
|
-
|
88
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
98
|
-
|
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('
|
101
|
-
|
102
|
-
|
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
|
105
|
-
|
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-
|
108
|
-
|
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-
|
111
|
-
|
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('
|
114
|
-
|
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-
|
117
|
-
|
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('--
|
120
|
-
|
121
|
-
|
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('--
|
124
|
-
|
125
|
-
|
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('-
|
128
|
-
|
129
|
-
|
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('-
|
132
|
-
|
133
|
-
|
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
|
-
|
137
|
-
|
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
|
-
|
141
|
-
|
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
|
-
|
604
|
+
build.chain{|c| c.load(imap_port: port_number) }
|
144
605
|
else
|
145
606
|
service_name = value
|
146
|
-
|
607
|
+
build.chain{|c| c.load(imap_port: service_name) }
|
147
608
|
end
|
148
609
|
end
|
149
|
-
options.on('--
|
150
|
-
|
151
|
-
|
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('--
|
154
|
-
|
155
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
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('--
|
163
|
-
|
164
|
-
|
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
|
-
|
652
|
+
build
|
168
653
|
end
|
169
|
-
module_function :
|
654
|
+
module_function :make_service_config
|
170
655
|
|
171
656
|
def cmd_server(options, args)
|
172
|
-
|
657
|
+
build = make_service_config(options)
|
173
658
|
options.parse!(args)
|
174
659
|
|
175
|
-
|
176
|
-
server.
|
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
|
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,
|
202
|
-
[ :imap_port,
|
203
|
-
[ :imap_ssl,
|
204
|
-
[ :
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
217
|
-
POST_MAIL_CONNECT_OPTION_LIST =
|
218
|
-
|
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,
|
227
|
-
[ :store_flag_deleted,
|
228
|
-
[ :store_flag_seen,
|
229
|
-
[ :store_flag_draft,
|
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, *
|
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
|
-
|
287
|
-
|
288
|
-
|
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
|
295
|
-
@options.on('-r', '--
|
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',
|
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-
|
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
|
-
|
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
|
-
|
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
|
-
[ [ :
|
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
|
-
'
|
979
|
+
'Obsoleted.'
|
415
980
|
],
|
416
981
|
[ :is_syslog,
|
417
|
-
|
982
|
+
nil,
|
418
983
|
'--[no-]syslog',
|
419
|
-
'
|
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
|
-
|
432
|
-
server_options.parse!(
|
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
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
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
|
-
|
1005
|
+
svc_conf = build.call
|
1006
|
+
pp svc_conf if $DEBUG
|
460
1007
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
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
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
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
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
-
|
487
|
-
|
488
|
-
if
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
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
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
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.
|
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
|
-
|
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
|
-
|
675
|
-
|
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
|
-
|
681
|
-
|
1250
|
+
svc_conf.load_yaml(path)
|
1251
|
+
load_service_config = true
|
682
1252
|
end
|
683
1253
|
options.parse!(args)
|
684
1254
|
|
685
|
-
unless (
|
1255
|
+
unless (load_service_config) then
|
686
1256
|
base_dir = args.shift or raise 'need for base directory.'
|
687
|
-
|
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
|
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, *
|
1297
|
+
passwd, *_optional = YAML.load_stream(STDIN)
|
728
1298
|
when 1
|
729
|
-
passwd, *
|
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.
|
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
|
-
|
1367
|
+
db.close
|
798
1368
|
end
|
799
1369
|
|
800
1370
|
0
|