rims 0.2.1

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