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.
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rims
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - TOKI Yoshinori
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-06 00:00:00.000000000 Z
11
+ date: 2019-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: riser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.7
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: logger-joint
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -90,7 +118,6 @@ files:
90
118
  - lib/rims/channel.rb
91
119
  - lib/rims/cksum_kvs.rb
92
120
  - lib/rims/cmd.rb
93
- - lib/rims/daemon.rb
94
121
  - lib/rims/db.rb
95
122
  - lib/rims/error.rb
96
123
  - lib/rims/gdbm_kvs.rb
@@ -104,17 +131,16 @@ files:
104
131
  - lib/rims/protocol/decoder.rb
105
132
  - lib/rims/protocol/parser.rb
106
133
  - lib/rims/rfc822.rb
107
- - lib/rims/server.rb
134
+ - lib/rims/service.rb
108
135
  - lib/rims/test.rb
109
136
  - lib/rims/version.rb
110
137
  - load_test/Rakefile
111
138
  - rims.gemspec
139
+ - test/cmd/test_command.rb
112
140
  - test/test_auth.rb
113
141
  - test/test_channel.rb
114
142
  - test/test_cksum_kvs.rb
115
- - test/test_config.rb
116
- - test/test_daemon_status_file.rb
117
- - test/test_daemon_waitpid.rb
143
+ - test/test_cmd.rb
118
144
  - test/test_db.rb
119
145
  - test/test_db_recovery.rb
120
146
  - test/test_error.rb
@@ -130,6 +156,7 @@ files:
130
156
  - test/test_protocol_request.rb
131
157
  - test/test_protocol_search.rb
132
158
  - test/test_rfc822.rb
159
+ - test/test_service.rb
133
160
  homepage: https://github.com/y10k/rims
134
161
  licenses:
135
162
  - MIT
@@ -149,17 +176,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
176
  - !ruby/object:Gem::Version
150
177
  version: '0'
151
178
  requirements: []
152
- rubygems_version: 3.0.1
179
+ rubygems_version: 3.0.3
153
180
  signing_key:
154
181
  specification_version: 4
155
182
  summary: RIMS is Ruby IMap Server
156
183
  test_files:
184
+ - test/cmd/test_command.rb
157
185
  - test/test_auth.rb
158
186
  - test/test_channel.rb
159
187
  - test/test_cksum_kvs.rb
160
- - test/test_config.rb
161
- - test/test_daemon_status_file.rb
162
- - test/test_daemon_waitpid.rb
188
+ - test/test_cmd.rb
163
189
  - test/test_db.rb
164
190
  - test/test_db_recovery.rb
165
191
  - test/test_error.rb
@@ -175,3 +201,4 @@ test_files:
175
201
  - test/test_protocol_request.rb
176
202
  - test/test_protocol_search.rb
177
203
  - test/test_rfc822.rb
204
+ - test/test_service.rb
@@ -1,338 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- require 'logger'
4
- require 'yaml'
5
-
6
- module RIMS
7
- class Daemon
8
- class ExclusiveStatusFile
9
- def initialize(filename)
10
- @filename = filename
11
- @file = nil
12
- @is_locked = false
13
- end
14
-
15
- def open
16
- if (block_given?) then
17
- open
18
- begin
19
- r = yield
20
- ensure
21
- close
22
- end
23
- return r
24
- end
25
-
26
- begin
27
- @file = File.open(@filename, File::WRONLY | File::CREAT, 0640)
28
- rescue SystemCallError
29
- @fiile = File.open(@filename, File::WRONLY)
30
- end
31
-
32
- self
33
- end
34
-
35
- def close
36
- @file.close
37
- self
38
- end
39
-
40
- def locked?
41
- @is_locked
42
- end
43
-
44
- def should_be_locked
45
- unless (locked?) then
46
- raise "not locked: #{@filename}"
47
- end
48
- self
49
- end
50
-
51
- def should_not_be_locked
52
- if (locked?) then
53
- raise "already locked: #{@filename}"
54
- end
55
- self
56
- end
57
-
58
- def lock
59
- should_not_be_locked
60
- unless (@file.flock(File::LOCK_EX | File::LOCK_NB)) then
61
- raise "locked by another process: #{@filename}"
62
- end
63
- @is_locked = true
64
- self
65
- end
66
-
67
- def unlock
68
- should_be_locked
69
- @file.flock(File::LOCK_UN)
70
- @is_locked = false
71
- self
72
- end
73
-
74
- def synchronize
75
- lock
76
- begin
77
- yield
78
- ensure
79
- unlock
80
- end
81
- end
82
-
83
- def write(text)
84
- should_be_locked
85
-
86
- @file.truncate(0)
87
- @file.syswrite(text)
88
-
89
- self
90
- end
91
- end
92
-
93
- class ReadableStatusFile
94
- def initialize(filename)
95
- @filename = filename
96
- @file = nil
97
- end
98
-
99
- def open
100
- if (block_given?) then
101
- open
102
- begin
103
- r = yield
104
- ensure
105
- close
106
- end
107
- return r
108
- end
109
-
110
- @file = File.open(@filename, File::RDONLY)
111
-
112
- self
113
- end
114
-
115
- def close
116
- @file.close
117
- self
118
- end
119
-
120
- def locked?
121
- if (@file.flock(File::LOCK_EX | File::LOCK_NB)) then
122
- @file.flock(File::LOCK_UN)
123
- false
124
- else
125
- true
126
- end
127
- end
128
-
129
- def should_be_locked
130
- unless (locked?) then
131
- raise "not locked: #{@filename}"
132
- end
133
- self
134
- end
135
-
136
- def read
137
- should_be_locked
138
- @file.seek(0)
139
- @file.read
140
- end
141
- end
142
-
143
- def self.new_status_file(filename, exclusive: false)
144
- if (exclusive) then
145
- ExclusiveStatusFile.new(filename)
146
- else
147
- ReadableStatusFile.new(filename)
148
- end
149
- end
150
-
151
- def self.make_stat_file_path(base_dir)
152
- File.join(base_dir, 'status')
153
- end
154
-
155
- RELOAD_SIGNAL_LIST = %w[ HUP ]
156
- RESTART_SIGNAL_LIST = %w[ USR1 ]
157
- STOP_SIGNAL_LIST = %w[ TERM INT ]
158
-
159
- RELOAD_SIGNAL = RELOAD_SIGNAL_LIST[0]
160
- RESTART_SIGNAL = RESTART_SIGNAL_LIST[0]
161
- STOP_SIGNAL = STOP_SIGNAL_LIST[0]
162
-
163
- SERVER_RESTART_INTERVAL_SECONDS = 5
164
-
165
- class ChildProcess
166
- # return self if child process has been existed.
167
- # return nil if no child process.
168
- def self.cleanup_terminated_process(logger)
169
- begin
170
- while (pid = Process.waitpid(-1))
171
- if ($?.exitstatus != 0) then
172
- logger.warn("aborted child process: #{pid} (#{$?.exitstatus})")
173
- end
174
- yield(pid) if block_given?
175
- end
176
- rescue Errno::ECHILD
177
- return
178
- end
179
-
180
- self
181
- end
182
-
183
- def initialize(logger=Logger.new(STDOUT))
184
- @logger = logger
185
- @pid = run{ yield }
186
- end
187
-
188
- attr_reader :pid
189
-
190
- def run
191
- begin
192
- pipe_in, pipe_out = IO.pipe
193
- pid = Process.fork{
194
- @logger.close
195
- pipe_in.close
196
-
197
- status_code = catch(:rims_daemon_child_process_stop) {
198
- for sig_name in RELOAD_SIGNAL_LIST + RESTART_SIGNAL_LIST
199
- Signal.trap(sig_name, :DEFAULT)
200
- end
201
- for sig_name in STOP_SIGNAL_LIST
202
- Signal.trap(sig_name) { throw(:rims_daemon_child_process_stop, 0) }
203
- end
204
-
205
- pipe_out.puts("child process (pid: #{$$}) is ready to go.")
206
- pipe_out.close
207
-
208
- yield
209
- }
210
- exit!(status_code)
211
- }
212
- rescue
213
- @logger.error("failed to fork new child process: #{$!}")
214
- return
215
- end
216
-
217
- begin
218
- pipe_out.close
219
- s = pipe_in.gets
220
- @logger.info("[child process message] #{s}") if $DEBUG
221
- pipe_in.close
222
- rescue
223
- @logger.error("failed to start new child process: #{$!}")
224
- begin
225
- Process.kill(STOP_SIGNAL, pid)
226
- rescue SystemCallError
227
- @logger.warn("failed to kill abnormal child process: #{$!}")
228
- end
229
- return
230
- end
231
-
232
- pid
233
- end
234
- private :run
235
-
236
- def forked?
237
- @pid != nil
238
- end
239
-
240
- def terminate
241
- begin
242
- Process.kill(STOP_SIGNAL, @pid)
243
- rescue SystemCallError
244
- @logger.warn("failed to terminate child process: #{@pid}")
245
- end
246
-
247
- nil
248
- end
249
- end
250
-
251
- def initialize(stat_file_path, logger=Logger.new(STDOUT), server_options: [])
252
- @stat_file = self.class.new_status_file(stat_file_path, exclusive: true)
253
- @logger = logger
254
- @server_options = server_options
255
- @server_running = true
256
- @server_process = nil
257
- end
258
-
259
- def new_server_process
260
- ChildProcess.new(@logger) { Cmd.run_cmd(%w[ server ] + @server_options) }
261
- end
262
- private :new_server_process
263
-
264
- def run
265
- @stat_file.open{
266
- @stat_file.synchronize{
267
- @stat_file.write({ 'pid' => $$ }.to_yaml)
268
- begin
269
- @logger.info('start daemon.')
270
- loop do
271
- break unless @server_running
272
-
273
- unless (@server_process && @server_process.forked?) then
274
- start_time = Time.now
275
- @server_process = new_server_process
276
- @logger.info("run server process: #{@server_process.pid}")
277
- end
278
-
279
- break unless @server_running
280
-
281
- ChildProcess.cleanup_terminated_process(@logger) do |pid|
282
- if (@server_process.pid == pid) then
283
- @server_process = nil
284
- end
285
- end
286
-
287
- break unless @server_running
288
-
289
- elapsed_seconds = Time.now - start_time
290
- the_rest_in_interval_seconds = SERVER_RESTART_INTERVAL_SECONDS - elapsed_seconds
291
- sleep(the_rest_in_interval_seconds) if (the_rest_in_interval_seconds > 0)
292
- end
293
- ensure
294
- if (@server_process && @server_process.forked?) then
295
- @server_process.terminate
296
- ChildProcess.cleanup_terminated_process(@logger)
297
- end
298
- @logger.info('stop daemon.')
299
- end
300
- }
301
- }
302
-
303
- self
304
- end
305
-
306
- # signal trap hook.
307
- # this method is not true reload.
308
- def reload_server
309
- restart_server
310
- end
311
-
312
- # signal trap hook.
313
- def restart_server
314
- @stat_file.should_be_locked
315
- if (@server_process && @server_process.forked?) then
316
- @server_process.terminate
317
- end
318
-
319
- self
320
- end
321
-
322
- # signal trap hook.
323
- def stop_server
324
- @stat_file.should_be_locked
325
- @server_running = false
326
- if (@server_process && @server_process.forked?) then
327
- @server_process.terminate
328
- end
329
-
330
- self
331
- end
332
- end
333
- end
334
-
335
- # Local Variables:
336
- # mode: Ruby
337
- # indent-tabs-mode: nil
338
- # End:
@@ -1,567 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- require 'etc'
4
- require 'forwardable'
5
- require 'logger'
6
- require 'pathname'
7
- require 'socket'
8
- require 'yaml'
9
-
10
- module RIMS
11
- class Multiplexor
12
- def initialize
13
- @obj_list = []
14
- end
15
-
16
- def add(object)
17
- @obj_list << object
18
- self
19
- end
20
-
21
- def method_missing(id, *args)
22
- for object in @obj_list
23
- r = object.__send__(id, *args)
24
- end
25
- r
26
- end
27
- end
28
-
29
- class Config
30
- extend Forwardable
31
-
32
- def self.relative_path?(path)
33
- Pathname.new(path).relative?
34
- end
35
-
36
- def_delegator 'self.class', :relative_path?
37
- private :relative_path?
38
-
39
- def self.load_plug_in_configuration(base_dir, config)
40
- if ((config.key? 'configuration') && (config.key? 'configuration_file')) then
41
- raise 'configuration conflict: configuration, configuraion_file'
42
- end
43
-
44
- if (config.key? 'configuration') then
45
- config['configuration']
46
- elsif (config.key? 'configuration_file') then
47
- config_file = config['configuration_file']
48
- if (relative_path? config_file) then
49
- config_path = File.join(base_dir, config_file)
50
- else
51
- config_path = config_file
52
- end
53
- YAML.load_file(config_path)
54
- else
55
- {}
56
- end
57
- end
58
-
59
- def initialize
60
- @config = {}
61
- end
62
-
63
- def load(config)
64
- @config.update(config)
65
- self
66
- end
67
-
68
- def load_config_yaml(path)
69
- load_config_from_base_dir(YAML.load_file(path), File.dirname(path))
70
- end
71
-
72
- def load_config_from_base_dir(config, base_dir)
73
- @config[:base_dir] = base_dir
74
- for name, value in config
75
- case (key_sym = name.to_sym)
76
- when :base_dir
77
- if (relative_path? value) then
78
- @config[:base_dir] = File.join(base_dir, value)
79
- else
80
- @config[:base_dir] = value
81
- end
82
- else
83
- @config[key_sym] = value
84
- end
85
- end
86
- self
87
- end
88
-
89
- # configuration entries.
90
- # * <tt>:base_dir</tt>
91
- #
92
- def base_dir
93
- @config[:base_dir] or raise 'not defined configuration entry: base_dir'
94
- end
95
-
96
- def through_server_params
97
- params = @config.dup
98
- params.delete(:base_dir)
99
- params
100
- end
101
-
102
- def setup_backward_compatibility
103
- [ [ :imap_host, :ip_addr ],
104
- [ :imap_port, :ip_port ]
105
- ].each do |new_namme, old_name|
106
- unless (@config.key? new_namme) then
107
- if (@config.key? old_name) then
108
- warn("warning: `#{old_name}' is obsoleted server configuration parameter and should be replaced to new parameter of `#{new_namme}'.")
109
- @config[new_namme] = @config.delete(old_name)
110
- end
111
- end
112
- end
113
-
114
- self
115
- end
116
-
117
- # configuration entry.
118
- # * <tt>load_libraries</tt>
119
- def setup_load_libraries
120
- lib_list = @config.delete(:load_libraries) || []
121
- for lib in lib_list
122
- require(lib)
123
- end
124
- end
125
-
126
- # configuration entries.
127
- # * <tt>:log_file</tt>
128
- # * <tt>:log_level</tt>
129
- # * <tt>:log_shift_age</tt>
130
- # * <tt>:log_shift_size</tt>
131
- # * <tt>:log_stdout</tt>
132
- #
133
- def logging_params
134
- log_file = @config.delete(:log_file) || Server::DEFAULT[:log_file]
135
- if (relative_path? log_file) then
136
- log_file_path = File.join(base_dir, log_file)
137
- else
138
- log_file_path = log_file
139
- end
140
-
141
- log_level = @config.delete(:log_level) || Server::DEFAULT[:log_level]
142
- log_level = log_level.upcase
143
- case (log_level)
144
- when 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
145
- log_level = Logger.const_get(log_level)
146
- else
147
- raise "unknown log level of logfile: #{log_level}"
148
- end
149
-
150
- log_stdout = @config.delete(:log_stdout) || Server::DEFAULT[:log_stdout]
151
- log_stdout = log_stdout.upcase
152
- case (log_stdout)
153
- when 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
154
- log_stdout = Logger.const_get(log_stdout)
155
- when 'QUIET'
156
- log_stdout = nil
157
- else
158
- raise "unknown log level of stdout: #{log_stdout}"
159
- end
160
-
161
- log_opt_args = []
162
- if (@config.key? :log_shift_age) then
163
- log_opt_args << @config.delete(:log_shift_age)
164
- log_opt_args << @config.delete(:log_shift_size) if (@config.key? :log_shift_size)
165
- else
166
- log_opt_args << 1 << @config.delete(:log_shift_size) if (@config.key? :log_shift_size)
167
- end
168
-
169
- { log_file: log_file_path,
170
- log_level: log_level,
171
- log_opt_args: log_opt_args,
172
- log_stdout: log_stdout
173
- }
174
- end
175
-
176
- def build_logger
177
- c = logging_params
178
- logger = Multiplexor.new
179
-
180
- if (c[:log_stdout]) then
181
- stdout_logger = Logger.new(STDOUT)
182
- stdout_logger.level = c[:log_stdout]
183
- logger.add(stdout_logger)
184
- end
185
-
186
- file_logger = Logger.new(c[:log_file], *c[:log_opt_args])
187
- file_logger.level = c[:log_level]
188
- logger.add(file_logger)
189
-
190
- logger
191
- end
192
-
193
- def key_value_store_params(db_type)
194
- kvs_conf = @config.delete(db_type) || {}
195
- key_value_store_type = @config.delete(:key_value_store_type) # for backward compatibility
196
- use_key_value_store_checksum = @config.delete(:use_key_value_store_checksum) # for backward compatibility
197
-
198
- kvs_type = kvs_conf['plug_in']
199
- kvs_type ||= key_value_store_type
200
- if (kvs_type) then
201
- origin_key_value_store = KeyValueStore::FactoryBuilder.get_plug_in(kvs_type)
202
- else
203
- origin_key_value_store = Server::DEFAULT[:key_value_store]
204
- end
205
- origin_config = self.class.load_plug_in_configuration(base_dir, kvs_conf)
206
-
207
- if (kvs_conf.key? 'use_checksum') then
208
- use_checksum = kvs_conf['use_checksum']
209
- elsif (! use_key_value_store_checksum.nil?) then
210
- use_checksum = use_key_value_store_checksum
211
- else
212
- use_checksum = Server::DEFAULT[:use_key_value_store_checksum]
213
- end
214
-
215
- middleware_key_value_store_list = []
216
- if (use_checksum) then
217
- middleware_key_value_store_list << Checksum_KeyValueStore
218
- end
219
-
220
- { origin_key_value_store: origin_key_value_store,
221
- origin_config: origin_config,
222
- middleware_key_value_store_list: middleware_key_value_store_list
223
- }
224
- end
225
-
226
- def build_key_value_store_factory(db_type)
227
- c = key_value_store_params(db_type)
228
- builder = KeyValueStore::FactoryBuilder.new
229
- builder.open{|name| c[:origin_key_value_store].open_with_conf(name, c[:origin_config]) }
230
- for middleware_key_value_store in c[:middleware_key_value_store_list]
231
- builder.use(middleware_key_value_store)
232
- end
233
- builder.factory
234
- end
235
-
236
- class << self
237
- def mkdir_from_base_dir(base_dir, path_name_list)
238
- unless (File.directory? base_dir) then
239
- raise "not found a base directory: #{base_dir}"
240
- end
241
-
242
- mkdir_count = 0
243
- make_path_list = [ base_dir ]
244
-
245
- for path_name in path_name_list
246
- make_path_list << path_name
247
- make_path = File.join(*make_path_list)
248
- begin
249
- Dir.mkdir(make_path)
250
- mkdir_count += 1
251
- rescue Errno::EEXIST
252
- unless (File.directory? make_path) then
253
- raise "not a directory: #{make_path}"
254
- end
255
- end
256
- end
257
-
258
- make_path if (mkdir_count > 0)
259
- end
260
-
261
- def make_key_value_store_path_name_list(mailbox_data_structure_version, unique_user_id, db_name: nil)
262
- if (mailbox_data_structure_version.empty?) then
263
- raise ArgumentError, 'too short mailbox data structure version.'
264
- end
265
- if (unique_user_id.length <= 2) then
266
- raise ArgumentError, 'too short unique user ID.'
267
- end
268
-
269
- bucket_dir_name = unique_user_id[0..1]
270
- store_dir_name = unique_user_id[2..-1]
271
- path_name_list = [ mailbox_data_structure_version, bucket_dir_name, store_dir_name ]
272
- path_name_list << db_name if db_name
273
-
274
- path_name_list
275
- end
276
-
277
- def make_key_value_store_path_from_base_dir(base_dir, mailbox_data_structure_version, unique_user_id, db_name: nil)
278
- path_name_list = [ base_dir ]
279
- path_name_list += make_key_value_store_path_name_list(mailbox_data_structure_version, unique_user_id, db_name: db_name)
280
- File.join(*path_name_list)
281
- end
282
-
283
- def make_key_value_store_parent_dir_from_base_dir(base_dir, mailbox_data_structure_version, unique_user_id)
284
- mkdir_from_base_dir(base_dir, make_key_value_store_path_name_list(mailbox_data_structure_version, unique_user_id))
285
- end
286
- end
287
-
288
- def make_key_value_store_path_from_base_dir(mailbox_data_structure_version, unique_user_id, db_name: nil)
289
- self.class.make_key_value_store_path_from_base_dir(base_dir, mailbox_data_structure_version, unique_user_id, db_name: db_name)
290
- end
291
-
292
- def make_key_value_store_parent_dir_from_base_dir(mailbox_data_structure_version, unique_user_id)
293
- self.class.make_key_value_store_parent_dir_from_base_dir(base_dir, mailbox_data_structure_version, unique_user_id)
294
- end
295
-
296
- # configuration entries.
297
- # * <tt>:hostname</tt>
298
- # * <tt>:username</tt>
299
- # * <tt>:password</tt>
300
- # * <tt>:user_list => [ { 'user' => 'username 1', 'pass' => 'password 1'}, { 'user' => 'username 2', 'pass' => 'password 2' } ]</tt>
301
- # * <tt>:authentication</tt>
302
- #
303
- def build_authentication
304
- hostname = @config.delete(:hostname) || Socket.gethostname
305
- auth = Authentication.new(hostname: hostname)
306
-
307
- user_list = []
308
- if (username = @config.delete(:username)) then
309
- password = @config.delete(:password) or raise 'not defined configuration entry: password'
310
- user_list << { 'user' => username, 'pass' => password }
311
- end
312
- if (@config.key? :user_list) then
313
- user_list += @config.delete(:user_list)
314
- end
315
- for user_entry in user_list
316
- auth.entry(user_entry['user'], user_entry['pass'])
317
- end
318
-
319
- if (auth_plug_in_list = @config.delete(:authentication)) then
320
- for auth_plug_in_entry in auth_plug_in_list
321
- name = auth_plug_in_entry['plug_in'] or raise 'undefined plug-in name.'
322
- config = self.class.load_plug_in_configuration(base_dir, auth_plug_in_entry)
323
- passwd_src = Authentication.get_plug_in(name, config)
324
- auth.add_plug_in(passwd_src)
325
- end
326
- end
327
-
328
- auth
329
- end
330
-
331
- def privilege_name2id(name)
332
- case (name)
333
- when Integer
334
- name
335
- when String
336
- begin
337
- yield(name)
338
- rescue
339
- if (name =~ /\A\d+\z/) then
340
- name.to_i
341
- else
342
- raise
343
- end
344
- end
345
- else
346
- raise TypeError, "not a process privilege name: #{name}"
347
- end
348
- end
349
- private :privilege_name2id
350
-
351
- # configuration entries.
352
- # * <tt>:process_privilege_user</tt>
353
- # * <tt>:process_privilege_group</tt>
354
- #
355
- def setup_privilege_params
356
- user = @config.delete(:process_privilege_user) || Server::DEFAULT[:process_privilege_uid]
357
- group = @config.delete(:process_privilege_group) || Server::DEFAULT[:process_privilege_gid]
358
-
359
- @config[:process_privilege_uid] = privilege_name2id(user) {|name| Etc.getpwnam(name).uid }
360
- @config[:process_privilege_gid] = privilege_name2id(group) {|name| Etc.getgrnam(name).gid }
361
-
362
- self
363
- end
364
-
365
- def build_server
366
- setup_backward_compatibility
367
-
368
- setup_load_libraries
369
- logger = build_logger
370
- meta_kvs_factory = build_key_value_store_factory(:meta_key_value_store)
371
- text_kvs_factory = build_key_value_store_factory(:text_key_value_store)
372
- auth = build_authentication
373
- setup_privilege_params
374
-
375
- make_parent_dir_and_logging = proc{|mailbox_data_structure_version, unique_user_id|
376
- if (make_dir_path = make_key_value_store_parent_dir_from_base_dir(mailbox_data_structure_version, unique_user_id)) then
377
- logger.debug("make a directory: #{make_dir_path}") if logger.debug?
378
- end
379
- }
380
-
381
- Server.new(kvs_meta_open: proc{|mailbox_data_structure_version, unique_user_id, db_name|
382
- make_parent_dir_and_logging.call(mailbox_data_structure_version, unique_user_id)
383
- kvs_path = make_key_value_store_path_from_base_dir(mailbox_data_structure_version, unique_user_id, db_name: db_name)
384
- logger.debug("meta data key-value store path: #{kvs_path}") if logger.debug?
385
- meta_kvs_factory.call(kvs_path)
386
- },
387
- kvs_text_open: proc{|mailbox_data_structure_version, unique_user_id, db_name|
388
- make_parent_dir_and_logging.call(mailbox_data_structure_version, unique_user_id)
389
- kvs_path = make_key_value_store_path_from_base_dir(mailbox_data_structure_version, unique_user_id, db_name: db_name)
390
- logger.debug("message data key-value store path: #{kvs_path}") if logger.debug?
391
- text_kvs_factory.call(kvs_path)
392
- },
393
- authentication: auth,
394
- logger: logger,
395
- **through_server_params)
396
- end
397
- end
398
-
399
- class BufferedWriter
400
- def initialize(output, buffer_limit=1024*16)
401
- @output = output
402
- @buffer_limit = buffer_limit
403
- @buffer_string = ''.b
404
- end
405
-
406
- def write_and_flush
407
- write_bytes = @output.write(@buffer_string)
408
- while (write_bytes < @buffer_string.bytesize)
409
- remaining_byte_range = write_bytes..-1
410
- write_bytes += @output.write(@buffer_string.byteslice(remaining_byte_range))
411
- end
412
- @buffer_string.clear
413
- @output.flush
414
- write_bytes
415
- end
416
- private :write_and_flush
417
-
418
- def write(string)
419
- @buffer_string << string.b
420
- write_and_flush if (@buffer_string.bytesize >= @buffer_limit)
421
- end
422
-
423
- def flush
424
- write_and_flush unless @buffer_string.empty?
425
- self
426
- end
427
-
428
- def <<(string)
429
- write(string)
430
- self
431
- end
432
- end
433
-
434
- class Server
435
- DEFAULT = {
436
- key_value_store: GDBM_KeyValueStore,
437
- use_key_value_store_checksum: true,
438
- imap_host: '0.0.0.0'.freeze,
439
- imap_port: 1430,
440
- send_buffer_limit: 1024 * 16,
441
- mail_delivery_user: '#postman'.freeze,
442
- process_privilege_uid: 65534,
443
- process_privilege_gid: 65534,
444
- log_file: 'imap.log'.freeze,
445
- log_level: 'INFO',
446
- log_stdout: 'INFO',
447
- read_lock_timeout_seconds: 30,
448
- write_lock_timeout_seconds: 30,
449
- cleanup_write_lock_timeout_seconds: 1
450
- }.freeze
451
-
452
- def initialize(kvs_meta_open: nil,
453
- kvs_text_open: nil,
454
- authentication: nil,
455
- imap_host: DEFAULT[:imap_host],
456
- imap_port: DEFAULT[:imap_port],
457
- send_buffer_limit: DEFAULT[:send_buffer_limit],
458
- mail_delivery_user: DEFAULT[:mail_delivery_user],
459
- process_privilege_uid: DEFAULT[:process_privilege_uid],
460
- process_privilege_gid: DEFAULT[:process_privilege_gid],
461
- read_lock_timeout_seconds: DEFAULT[:read_lock_timeout_seconds],
462
- write_lock_timeout_seconds: DEFAULT[:write_lock_timeout_seconds],
463
- cleanup_write_lock_timeout_seconds: DEFAULT[:cleanup_write_lock_timeout_seconds],
464
- logger: Logger.new(STDOUT))
465
- begin
466
- kvs_meta_open or raise ArgumentError, 'need for a keyword argument: kvs_meta_open'
467
- kvs_text_open or raise ArgumentError, 'need for a keyword argument: kvs_text_open'
468
- @authentication = authentication or raise ArgumentError, 'need for a keyword argument: authentication'
469
-
470
- @imap_host = imap_host
471
- @imap_port = imap_port
472
- @send_buffer_limit = send_buffer_limit
473
- @mail_delivery_user = mail_delivery_user
474
-
475
- @process_privilege_uid = process_privilege_uid
476
- @process_privilege_gid = process_privilege_gid
477
-
478
- @read_lock_timeout_seconds = read_lock_timeout_seconds
479
- @write_lock_timeout_seconds = write_lock_timeout_seconds
480
- @cleanup_write_lock_timeout_seconds = cleanup_write_lock_timeout_seconds
481
-
482
- @logger = logger
483
- @mail_store_pool = MailStore.build_pool(kvs_meta_open, kvs_text_open)
484
- rescue
485
- logger.fatal($!) rescue StandardError
486
- raise
487
- end
488
- end
489
-
490
- def ipaddr_log(addr_list)
491
- addr_list.map{|i| "[#{i}]" }.join('')
492
- end
493
- private :ipaddr_log
494
-
495
- def start
496
- @logger.info('start server.')
497
- @authentication.start_plug_in(@logger)
498
- @logger.info("open socket: #{@imap_host}:#{@imap_port}")
499
- sv_sock = TCPServer.new(@imap_host, @imap_port)
500
-
501
- begin
502
- @logger.info("opened: #{ipaddr_log(sv_sock.addr)}")
503
-
504
- if (Process.euid == 0) then
505
- Process::Sys.setgid(@process_privilege_gid)
506
- Process::Sys.setuid(@process_privilege_uid)
507
- end
508
-
509
- @logger.info("process ID: #{$$}")
510
- process_user = Etc.getpwuid(Process.euid).name rescue ''
511
- @logger.info("process privilege user: #{process_user}(#{Process.euid})")
512
- process_group = Etc.getgrgid(Process.egid).name rescue ''
513
- @logger.info("process privilege group: #{process_group}(#{Process.egid})")
514
-
515
- loop do
516
- Thread.start(sv_sock.accept) {|cl_sock|
517
- begin
518
- @logger.info("accept client: #{ipaddr_log(cl_sock.peeraddr(false))}")
519
- decoder = Protocol::Decoder.new_decoder(@mail_store_pool, @authentication, @logger,
520
- mail_delivery_user: @mail_delivery_user,
521
- read_lock_timeout_seconds: @read_lock_timeout_seconds,
522
- write_lock_timeout_seconds: @write_lock_timeout_seconds,
523
- cleanup_write_lock_timeout_seconds: @cleanup_write_lock_timeout_seconds)
524
- Protocol::Decoder.repl(decoder, cl_sock, BufferedWriter.new(cl_sock, @send_buffer_limit), @logger)
525
- ensure
526
- Error.suppress_2nd_error_at_resource_closing(logger: @logger) { cl_sock.close }
527
- end
528
- }
529
- end
530
- ensure
531
- @logger.info("close socket: #{ipaddr_log(sv_sock.addr)}")
532
- sv_sock.close
533
- @authentication.stop_plug_in(@logger)
534
- end
535
-
536
- self
537
- rescue
538
- @logger.error($!)
539
- raise
540
- ensure
541
- @logger.info('stop sever.')
542
- end
543
- end
544
- end
545
-
546
- if ($0 == __FILE__) then
547
- require 'pp' if $DEBUG
548
- require 'rims'
549
-
550
- if (ARGV.length != 1) then
551
- STDERR.puts "usage: #{$0} config.yml"
552
- exit(1)
553
- end
554
-
555
- c = RIMS::Config.new
556
- c.load_config_yaml(ARGV[0])
557
- c.setup
558
- pp c.config if $DEBUG
559
-
560
- server = RIMS::Server.new(**c.config)
561
- server.start
562
- end
563
-
564
- # Local Variables:
565
- # mode: Ruby
566
- # indent-tabs-mode: nil
567
- # End: