rims 0.2.1
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 +7 -0
- data/.gitignore +17 -0
- data/ChangeLog +379 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +566 -0
- data/Rakefile +29 -0
- data/bin/rims +11 -0
- data/lib/rims.rb +45 -0
- data/lib/rims/auth.rb +133 -0
- data/lib/rims/cksum_kvs.rb +68 -0
- data/lib/rims/cmd.rb +809 -0
- data/lib/rims/daemon.rb +338 -0
- data/lib/rims/db.rb +793 -0
- data/lib/rims/error.rb +23 -0
- data/lib/rims/gdbm_kvs.rb +76 -0
- data/lib/rims/hash_kvs.rb +66 -0
- data/lib/rims/kvs.rb +101 -0
- data/lib/rims/lock.rb +151 -0
- data/lib/rims/mail_store.rb +663 -0
- data/lib/rims/passwd.rb +251 -0
- data/lib/rims/pool.rb +88 -0
- data/lib/rims/protocol.rb +71 -0
- data/lib/rims/protocol/decoder.rb +1469 -0
- data/lib/rims/protocol/parser.rb +1114 -0
- data/lib/rims/rfc822.rb +456 -0
- data/lib/rims/server.rb +567 -0
- data/lib/rims/test.rb +391 -0
- data/lib/rims/version.rb +11 -0
- data/load_test/Rakefile +93 -0
- data/rims.gemspec +38 -0
- data/test/test_auth.rb +174 -0
- data/test/test_cksum_kvs.rb +121 -0
- data/test/test_config.rb +533 -0
- data/test/test_daemon_status_file.rb +169 -0
- data/test/test_daemon_waitpid.rb +72 -0
- data/test/test_db.rb +602 -0
- data/test/test_db_recovery.rb +732 -0
- data/test/test_error.rb +97 -0
- data/test/test_gdbm_kvs.rb +32 -0
- data/test/test_hash_kvs.rb +116 -0
- data/test/test_lock.rb +161 -0
- data/test/test_mail_store.rb +750 -0
- data/test/test_passwd.rb +203 -0
- data/test/test_protocol.rb +91 -0
- data/test/test_protocol_auth.rb +121 -0
- data/test/test_protocol_decoder.rb +6490 -0
- data/test/test_protocol_fetch.rb +994 -0
- data/test/test_protocol_request.rb +332 -0
- data/test/test_protocol_search.rb +974 -0
- data/test/test_rfc822.rb +696 -0
- metadata +174 -0
data/lib/rims/server.rb
ADDED
@@ -0,0 +1,567 @@
|
|
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:
|