rims 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|