rims 0.2.2 → 0.2.3

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