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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/ChangeLog +379 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +566 -0
  7. data/Rakefile +29 -0
  8. data/bin/rims +11 -0
  9. data/lib/rims.rb +45 -0
  10. data/lib/rims/auth.rb +133 -0
  11. data/lib/rims/cksum_kvs.rb +68 -0
  12. data/lib/rims/cmd.rb +809 -0
  13. data/lib/rims/daemon.rb +338 -0
  14. data/lib/rims/db.rb +793 -0
  15. data/lib/rims/error.rb +23 -0
  16. data/lib/rims/gdbm_kvs.rb +76 -0
  17. data/lib/rims/hash_kvs.rb +66 -0
  18. data/lib/rims/kvs.rb +101 -0
  19. data/lib/rims/lock.rb +151 -0
  20. data/lib/rims/mail_store.rb +663 -0
  21. data/lib/rims/passwd.rb +251 -0
  22. data/lib/rims/pool.rb +88 -0
  23. data/lib/rims/protocol.rb +71 -0
  24. data/lib/rims/protocol/decoder.rb +1469 -0
  25. data/lib/rims/protocol/parser.rb +1114 -0
  26. data/lib/rims/rfc822.rb +456 -0
  27. data/lib/rims/server.rb +567 -0
  28. data/lib/rims/test.rb +391 -0
  29. data/lib/rims/version.rb +11 -0
  30. data/load_test/Rakefile +93 -0
  31. data/rims.gemspec +38 -0
  32. data/test/test_auth.rb +174 -0
  33. data/test/test_cksum_kvs.rb +121 -0
  34. data/test/test_config.rb +533 -0
  35. data/test/test_daemon_status_file.rb +169 -0
  36. data/test/test_daemon_waitpid.rb +72 -0
  37. data/test/test_db.rb +602 -0
  38. data/test/test_db_recovery.rb +732 -0
  39. data/test/test_error.rb +97 -0
  40. data/test/test_gdbm_kvs.rb +32 -0
  41. data/test/test_hash_kvs.rb +116 -0
  42. data/test/test_lock.rb +161 -0
  43. data/test/test_mail_store.rb +750 -0
  44. data/test/test_passwd.rb +203 -0
  45. data/test/test_protocol.rb +91 -0
  46. data/test/test_protocol_auth.rb +121 -0
  47. data/test/test_protocol_decoder.rb +6490 -0
  48. data/test/test_protocol_fetch.rb +994 -0
  49. data/test/test_protocol_request.rb +332 -0
  50. data/test/test_protocol_search.rb +974 -0
  51. data/test/test_rfc822.rb +696 -0
  52. metadata +174 -0
@@ -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: