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/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/clean'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rdoc/task'
|
7
|
+
|
8
|
+
Rake::TestTask.new do |task|
|
9
|
+
if ((ENV.key? 'RUBY_DEBUG') && (! ENV['RUBY_DEBUG'].empty?)) then
|
10
|
+
task.ruby_opts << '-d'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::RDocTask.new do |rd|
|
15
|
+
rd.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Build README.html from markdown source.'
|
19
|
+
task :readme => %w[ README.html ]
|
20
|
+
|
21
|
+
file 'README.html' => [ 'README.md' ] do
|
22
|
+
sh "pandoc --from=markdown --to=html5 --standalone --self-contained --css=$HOME/.pandoc/github.css --output=README.html README.md"
|
23
|
+
end
|
24
|
+
CLOBBER.include 'README.html'
|
25
|
+
|
26
|
+
# Local Variables:
|
27
|
+
# mode: Ruby
|
28
|
+
# indent-tabs-mode: nil
|
29
|
+
# End:
|
data/bin/rims
ADDED
data/lib/rims.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "rims/version"
|
4
|
+
|
5
|
+
module RIMS
|
6
|
+
autoload :Authentication, 'rims/auth'
|
7
|
+
autoload :BufferedWriter, 'rims/server'
|
8
|
+
autoload :Checksum_KeyValueStore, 'rims/cksum_kvs'
|
9
|
+
autoload :Cmd, 'rims/cmd'
|
10
|
+
autoload :Config, 'rims/server'
|
11
|
+
autoload :DB, 'rims/db'
|
12
|
+
autoload :Daemon, 'rims/daemon'
|
13
|
+
autoload :Error, 'rims/error'
|
14
|
+
autoload :GDBM_KeyValueStore, 'rims/gdbm_kvs'
|
15
|
+
autoload :GlobalDB, 'rims/db'
|
16
|
+
autoload :Hash_KeyValueStore, 'rims/hash_kvs'
|
17
|
+
autoload :IllegalLockError, 'rims/lock'
|
18
|
+
autoload :KeyValueStore, 'rims/kvs'
|
19
|
+
autoload :LockError, 'rims/lock'
|
20
|
+
autoload :MailFolder, 'rims/mail_store'
|
21
|
+
autoload :MailStore, 'rims/mail_store'
|
22
|
+
autoload :MailStoreHolder, 'rims/mail_store'
|
23
|
+
autoload :MailboxDB, 'rims/db'
|
24
|
+
autoload :MessageDB, 'rims/db'
|
25
|
+
autoload :MessageSetSyntaxError, 'rims/protocol'
|
26
|
+
autoload :Multiplexor, 'rims/server'
|
27
|
+
autoload :ObjectPool, 'rims/pool'
|
28
|
+
autoload :Password, 'rims/passwd'
|
29
|
+
autoload :Protocol, 'rims/protocol'
|
30
|
+
autoload :ProtocolError, 'rims/protocol'
|
31
|
+
autoload :RFC822, 'rims/rfc822'
|
32
|
+
autoload :ReadLockError, 'rims/lock'
|
33
|
+
autoload :ReadLockTimeoutError, 'rims/lock'
|
34
|
+
autoload :ReadWriteLock, 'rims/lock'
|
35
|
+
autoload :Server, 'rims/server'
|
36
|
+
autoload :SyntaxError, 'rims/protocol'
|
37
|
+
autoload :Test, 'rims/test'
|
38
|
+
autoload :WriteLockError, 'rims/lock'
|
39
|
+
autoload :WriteLockTimeoutError, 'rims/lock'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Local Variables:
|
43
|
+
# mode: Ruby
|
44
|
+
# indent-tabs-mode: nil
|
45
|
+
# End:
|
data/lib/rims/auth.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
require 'openssl'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
module RIMS
|
8
|
+
class Authentication
|
9
|
+
PLUG_IN = {} # :nodoc:
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def unique_user_id(username)
|
13
|
+
Digest::SHA256.hexdigest(username).freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_time_source
|
17
|
+
proc{ Time.now }
|
18
|
+
end
|
19
|
+
|
20
|
+
def make_random_string_source
|
21
|
+
proc{ SecureRandom.uuid }
|
22
|
+
end
|
23
|
+
|
24
|
+
def cram_md5_server_challenge_data(hostname, time_source, random_string_source)
|
25
|
+
s = random_string_source.call
|
26
|
+
t = time_source.call
|
27
|
+
"#{s}.#{t.to_i}@#{hostname}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def hmac_md5_hexdigest(key, data)
|
31
|
+
OpenSSL::HMAC.hexdigest('md5', key, data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_plug_in(name, klass)
|
35
|
+
PLUG_IN[name] = klass
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_plug_in(name, config)
|
40
|
+
klass = PLUG_IN[name] or raise KeyError, "not found a password source plug-in: #{name}"
|
41
|
+
klass.build_from_conf(config)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(hostname: 'rims',
|
46
|
+
time_source: Authentication.make_time_source,
|
47
|
+
random_string_source: Authentication.make_random_string_source)
|
48
|
+
@hostname = hostname
|
49
|
+
@time_source = time_source
|
50
|
+
@random_string_source = random_string_source
|
51
|
+
@capability = %w[ PLAIN CRAM-MD5 ]
|
52
|
+
@plain_src = Password::PlainSource.new
|
53
|
+
@passwd_src_list = [ @plain_src ]
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :hostname
|
57
|
+
attr_reader :capability
|
58
|
+
|
59
|
+
def add_plug_in(passwd_src)
|
60
|
+
unless (passwd_src.raw_password?) then
|
61
|
+
@capability.delete('CRAM-MD5')
|
62
|
+
end
|
63
|
+
@passwd_src_list << passwd_src
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def start_plug_in(logger)
|
68
|
+
for passwd_src in @passwd_src_list
|
69
|
+
logger.info("start password source plug-in: #{passwd_src.class}")
|
70
|
+
passwd_src.logger = logger
|
71
|
+
passwd_src.start
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop_plug_in(logger)
|
76
|
+
for passwd_src in @passwd_src_list.reverse
|
77
|
+
logger.info("stop password source plug-in: #{passwd_src.class}")
|
78
|
+
passwd_src.stop
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def entry(username, password)
|
83
|
+
@plain_src.entry(username, password)
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def user?(username)
|
88
|
+
@passwd_src_list.any?{|passwd_src| passwd_src.user? username }
|
89
|
+
end
|
90
|
+
|
91
|
+
def authenticate_login(username, password)
|
92
|
+
for passwd_src in @passwd_src_list
|
93
|
+
if (passwd_src.compare_password(username, password)) then
|
94
|
+
return username
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def authenticate_plain(client_response_data)
|
102
|
+
authz_id, authc_id, password = client_response_data.split("\0", 3)
|
103
|
+
if (authz_id.empty? || (authz_id == authc_id)) then
|
104
|
+
authenticate_login(authc_id, password)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def cram_md5_server_challenge_data
|
109
|
+
self.class.cram_md5_server_challenge_data(@hostname, @time_source, @random_string_source)
|
110
|
+
end
|
111
|
+
|
112
|
+
def authenticate_cram_md5(server_challenge_data, client_response_data)
|
113
|
+
username, client_hmac_result_data = client_response_data.split(' ', 2)
|
114
|
+
for passwd_src in @passwd_src_list
|
115
|
+
if (passwd_src.raw_password?) then
|
116
|
+
if (key = passwd_src.fetch_password(username)) then
|
117
|
+
server_hmac_result_data = Authentication.hmac_md5_hexdigest(key, server_challenge_data)
|
118
|
+
if (client_hmac_result_data == server_hmac_result_data) then
|
119
|
+
return username
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Local Variables:
|
131
|
+
# mode: Ruby
|
132
|
+
# indent-tabs-mode: nil
|
133
|
+
# End:
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module RIMS
|
6
|
+
class Checksum_KeyValueStore < KeyValueStore
|
7
|
+
def initialize(kvs)
|
8
|
+
@kvs = kvs
|
9
|
+
end
|
10
|
+
|
11
|
+
def md5_cksum_parse(key, s)
|
12
|
+
if (s) then
|
13
|
+
s =~ /\Amd5 (\S+?)\n/ or raise "checksum format error at key: #{key}"
|
14
|
+
md5_cksum = $1
|
15
|
+
value = $'
|
16
|
+
if (Digest::MD5.hexdigest(value) != md5_cksum) then
|
17
|
+
raise "checksum error at key: #{key}"
|
18
|
+
end
|
19
|
+
|
20
|
+
value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
private :md5_cksum_parse
|
24
|
+
|
25
|
+
def [](key)
|
26
|
+
md5_cksum_parse(key, @kvs[key])
|
27
|
+
end
|
28
|
+
|
29
|
+
def []=(key, value)
|
30
|
+
@kvs[key] = "md5 #{Digest::MD5.hexdigest(value)}\n#{value}"
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(key)
|
35
|
+
md5_cksum_parse(key, @kvs.delete(key))
|
36
|
+
end
|
37
|
+
|
38
|
+
def key?(key)
|
39
|
+
@kvs.key? key
|
40
|
+
end
|
41
|
+
|
42
|
+
def each_key(&block)
|
43
|
+
return enum_for(:each_key) unless block_given?
|
44
|
+
@kvs.each_key(&block)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def sync
|
49
|
+
@kvs.sync
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def close
|
54
|
+
@kvs.close
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy
|
59
|
+
@kvs.destroy
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Local Variables:
|
66
|
+
# mode: Ruby
|
67
|
+
# indent-tabs-mode: nil
|
68
|
+
# End:
|
data/lib/rims/cmd.rb
ADDED
@@ -0,0 +1,809 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'net/imap'
|
5
|
+
require 'optparse'
|
6
|
+
require 'pp'if $DEBUG
|
7
|
+
require 'syslog'
|
8
|
+
require 'syslog/logger'
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
module RIMS
|
12
|
+
module Cmd
|
13
|
+
CMDs = {}
|
14
|
+
|
15
|
+
def self.command_function(method_name, description)
|
16
|
+
module_function(method_name)
|
17
|
+
method_name = method_name.to_s
|
18
|
+
unless (method_name =~ /^cmd_/) then
|
19
|
+
raise "invalid command function name: #{method_name}"
|
20
|
+
end
|
21
|
+
cmd_name = $'.gsub(/_/, '-')
|
22
|
+
CMDs[cmd_name] = { function: method_name.to_sym, description: description }
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_cmd(args)
|
26
|
+
options = OptionParser.new
|
27
|
+
if (args.empty?) then
|
28
|
+
cmd_help(options, args)
|
29
|
+
return 1
|
30
|
+
end
|
31
|
+
|
32
|
+
cmd_name = args.shift
|
33
|
+
pp cmd_name if $DEBUG
|
34
|
+
pp args if $DEBUG
|
35
|
+
|
36
|
+
cmd_entry = CMDs[cmd_name] or raise "unknown command: #{cmd_name}. Run `#{options.program_name} help'."
|
37
|
+
options.program_name += " #{cmd_name}"
|
38
|
+
send(cmd_entry[:function], options, args)
|
39
|
+
end
|
40
|
+
module_function :run_cmd
|
41
|
+
|
42
|
+
def cmd_help(options, args)
|
43
|
+
show_debug_command = false
|
44
|
+
options.on('--show-debug-command', 'Show command for debug in help message. At default, debug command is hidden.') do
|
45
|
+
show_debug_command = true
|
46
|
+
end
|
47
|
+
options.parse!(args)
|
48
|
+
|
49
|
+
STDERR.puts "usage: #{File.basename($0)} command options"
|
50
|
+
STDERR.puts ""
|
51
|
+
STDERR.puts "commands:"
|
52
|
+
w = CMDs.keys.map{|k| k.length }.max + 4
|
53
|
+
fmt = " %- #{w}s%s"
|
54
|
+
CMDs.each do |cmd_name, cmd_entry|
|
55
|
+
if ((! show_debug_command) && (cmd_name =~ /^debug/)) then
|
56
|
+
next
|
57
|
+
end
|
58
|
+
STDERR.puts format(fmt, cmd_name, cmd_entry[:description])
|
59
|
+
end
|
60
|
+
STDERR.puts ""
|
61
|
+
STDERR.puts "command help options:"
|
62
|
+
STDERR.puts " -h, --help"
|
63
|
+
0
|
64
|
+
end
|
65
|
+
command_function :cmd_help, "Show this message."
|
66
|
+
|
67
|
+
def cmd_version(options, args)
|
68
|
+
options.parse!(args)
|
69
|
+
puts RIMS::VERSION
|
70
|
+
0
|
71
|
+
end
|
72
|
+
command_function :cmd_version, 'Show software version.'
|
73
|
+
|
74
|
+
def make_server_config(options)
|
75
|
+
conf = RIMS::Config.new
|
76
|
+
conf.load(base_dir: Dir.getwd)
|
77
|
+
|
78
|
+
options.on('-h', '--help', 'Show this message.') do
|
79
|
+
puts options
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
options.on('-f', '--config-yaml=CONFIG_FILE',
|
83
|
+
"Load optional parameters from CONFIG_FILE.") do |path|
|
84
|
+
conf.load_config_yaml(path)
|
85
|
+
end
|
86
|
+
options.on('-d', '--base-dir=DIR',
|
87
|
+
"Directory that places log file, mailbox database, etc. default is current directory.") do |path|
|
88
|
+
conf.load(base_dir: path)
|
89
|
+
end
|
90
|
+
level_list = %w[ debug info warn error fatal ]
|
91
|
+
stdout_list = level_list + %w[ quiet ]
|
92
|
+
options.on('-v', '--log-stdout=LEVEL', stdout_list,
|
93
|
+
"Stdout logging level (#{stdout_list.join(' ')}). default is `info'.") do |level|
|
94
|
+
conf.load(log_stdout: level)
|
95
|
+
end
|
96
|
+
options.on('--log-file=FILE',
|
97
|
+
"Name of log file. the directory part preceding file name is ignored. default is `imap.log'.") do |path|
|
98
|
+
conf.load(log_file: path)
|
99
|
+
end
|
100
|
+
options.on('-l', '--log-level=LEVEL', level_list,
|
101
|
+
"Logging level (#{level_list.join(' ')}). default is `info'.") do |level|
|
102
|
+
conf.load(log_level: level)
|
103
|
+
end
|
104
|
+
options.on('--log-shift-age=NUMBER', Integer, 'Number of old log files to keep.') do |num|
|
105
|
+
conf.load(log_shift_age: num)
|
106
|
+
end
|
107
|
+
options.on('--log-shift-age-daily', 'Frequency of daily log rotation.') do
|
108
|
+
conf.load(log_shift_age: 'daily')
|
109
|
+
end
|
110
|
+
options.on('--log-shift-age-weekly', 'Frequency of weekly log rotation.') do
|
111
|
+
conf.load(log_shift_age: 'weekly')
|
112
|
+
end
|
113
|
+
options.on('--log-shift-age-monthly', 'Frequency of monthly log rotation.') do
|
114
|
+
conf.load(log_shift_age: 'monthly')
|
115
|
+
end
|
116
|
+
options.on('--log-shift-size=SIZE', Integer, 'Maximum logfile size.') do |size|
|
117
|
+
conf.load(log_shift_size: size)
|
118
|
+
end
|
119
|
+
options.on('--kvs-type=TYPE', %w[ gdbm ],
|
120
|
+
"Choose the key-value store type of mailbox database. load plug-in on config.yml.") do |type|
|
121
|
+
conf.load(key_value_store_type: type)
|
122
|
+
end
|
123
|
+
options.on('--[no-]use-kvs-cksum',
|
124
|
+
"Enable/disable data checksum at key-value store. default is enabled.") do |use|
|
125
|
+
conf.load(use_key_value_store_checksum: use)
|
126
|
+
end
|
127
|
+
options.on('-u', '--username=NAME',
|
128
|
+
"Username to login IMAP server. required parameter to start server.") do |name|
|
129
|
+
conf.load(username: name)
|
130
|
+
end
|
131
|
+
options.on('-w', '--password=PASS',
|
132
|
+
"Password to login IMAP server. required parameter to start server.") do |pass|
|
133
|
+
conf.load(password: pass)
|
134
|
+
end
|
135
|
+
options.on('--imap-host=HOSTNAME',
|
136
|
+
"IMAP server hostname or IP address for the server to bind. default is `#{Server::DEFAULT[:imap_host]}'.") do |host|
|
137
|
+
conf.load(imap_host: host)
|
138
|
+
end
|
139
|
+
options.on('--imap-port=PORT',
|
140
|
+
"IMAP server port number or service name for the server to bind. default is `#{Server::DEFAULT[:imap_port]}'.") do |value|
|
141
|
+
if (value =~ /\A\d+\z/) then
|
142
|
+
port_number = value.to_i
|
143
|
+
conf.load(imap_port: port_number)
|
144
|
+
else
|
145
|
+
service_name = value
|
146
|
+
conf.load(imap_port: service_name)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
options.on('--privilege-user=NAME',
|
150
|
+
"Privilege user name or ID for server process. default is #{Server::DEFAULT[:process_privilege_uid]}.") do |name|
|
151
|
+
conf.load(process_privilege_user: name)
|
152
|
+
end
|
153
|
+
options.on('--privilege-group=NAME',
|
154
|
+
"Privilege group name or ID for server process. default is #{Server::DEFAULT[:process_privilege_gid]}.") do |name|
|
155
|
+
conf.load(process_privilege_user: name)
|
156
|
+
end
|
157
|
+
|
158
|
+
options.on('--ip-addr=IP_ADDR', 'obsoleted.') do |ip_addr|
|
159
|
+
warn("warning: `--ip-addr=IP_ADDR' is obsoleted option and should use `--imap-host=HOSTNAME'.")
|
160
|
+
conf.load(ip_addr: ip_addr)
|
161
|
+
end
|
162
|
+
options.on('--ip-port=PORT', Integer, 'obsoleted.') do |port|
|
163
|
+
warn("warning: `--ip-port=PORT' is obsoleted option and should use `--imap-port=PORT'.")
|
164
|
+
conf.load(ip_port: port)
|
165
|
+
end
|
166
|
+
|
167
|
+
conf
|
168
|
+
end
|
169
|
+
module_function :make_server_config
|
170
|
+
|
171
|
+
def cmd_server(options, args)
|
172
|
+
conf = make_server_config(options)
|
173
|
+
options.parse!(args)
|
174
|
+
|
175
|
+
server = conf.build_server
|
176
|
+
server.start
|
177
|
+
|
178
|
+
0
|
179
|
+
end
|
180
|
+
command_function :cmd_server, "Run IMAP server."
|
181
|
+
|
182
|
+
def imap_res2str(imap_response)
|
183
|
+
"#{imap_response.name} #{imap_response.data.text}"
|
184
|
+
end
|
185
|
+
module_function :imap_res2str
|
186
|
+
|
187
|
+
class Config
|
188
|
+
def imap_res2str(imap_response)
|
189
|
+
Cmd.imap_res2str(imap_response)
|
190
|
+
end
|
191
|
+
private :imap_res2str
|
192
|
+
|
193
|
+
IMAP_AUTH_TYPE_LIST = %w[ login plain cram-md5 ]
|
194
|
+
MAIL_DATE_PLACE_LIST = [ :servertime, :localtime, :filetime, :mailheader ]
|
195
|
+
|
196
|
+
VERBOSE_OPTION_LIST = [
|
197
|
+
[ :verbose, false, '-v', '--[no-]verbose', "Enable verbose messages. default is no verbose." ]
|
198
|
+
]
|
199
|
+
|
200
|
+
def self.make_imap_connect_option_list(imap_host: 'localhost', imap_port: 143, imap_ssl: false, auth_type: 'login', username: nil)
|
201
|
+
[ [ :imap_host, imap_host, '-n', '--host=HOSTNAME', "Hostname or IP address to connect IMAP server. default is `#{imap_host}'." ],
|
202
|
+
[ :imap_port, imap_port, '-o', '--port=PORT', Integer, "Server port number or service name to connect IMAP server. default is #{imap_port}." ],
|
203
|
+
[ :imap_ssl, imap_ssl, '-s', '--[no-]use-ssl', "Enable SSL/TLS connection. default is #{imap_ssl ? 'enabled' : 'disabled'}." ],
|
204
|
+
[ :username, username, '-u', '--username=NAME',
|
205
|
+
"Username to login IMAP server. " + if (username) then
|
206
|
+
"default is `#{username}'."
|
207
|
+
else
|
208
|
+
"required parameter to connect server."
|
209
|
+
end ],
|
210
|
+
[ :password, nil, '-w', '--password=PASS', "Password to login IMAP server. required parameter to connect server." ],
|
211
|
+
[ :auth_type, auth_type, '--auth-type=METHOD', IMAP_AUTH_TYPE_LIST,
|
212
|
+
"Choose authentication method type (#{IMAP_AUTH_TYPE_LIST.join(' ')}). default is `#{auth_type}'." ]
|
213
|
+
]
|
214
|
+
end
|
215
|
+
|
216
|
+
IMAP_CONNECT_OPTION_LIST = self.make_imap_connect_option_list
|
217
|
+
POST_MAIL_CONNECT_OPTION_LIST = self.make_imap_connect_option_list(imap_port: Server::DEFAULT[:imap_port],
|
218
|
+
username: Server::DEFAULT[:mail_delivery_user])
|
219
|
+
|
220
|
+
IMAP_MAILBOX_OPTION_LIST = [
|
221
|
+
[ :mailbox, 'INBOX', '-m', '--mailbox=NAME', "Set mailbox name to append messages. default is `INBOX'." ]
|
222
|
+
]
|
223
|
+
|
224
|
+
IMAP_STORE_FLAG_OPTION_LIST = [
|
225
|
+
[ :store_flag_answered, false, '--[no-]store-flag-answered', "Store answered flag on appending messages to mailbox. default is no flag." ],
|
226
|
+
[ :store_flag_flagged, false, '--[no-]store-flag-flagged', "Store flagged flag on appending messages to mailbox. default is no flag." ],
|
227
|
+
[ :store_flag_deleted, false, '--[no-]store-flag-deleted', "Store deleted flag on appending messages to mailbox. default is no flag." ],
|
228
|
+
[ :store_flag_seen, false, '--[no-]store-flag-seen', "Store seen flag on appending messages to mailbox. default is no flag." ],
|
229
|
+
[ :store_flag_draft, false, '--[no-]store-flag-draft', "Store draft flag on appending messages to mailbox. default is no flag." ]
|
230
|
+
]
|
231
|
+
|
232
|
+
MAIL_DATE_OPTION_LIST = [
|
233
|
+
[ :look_for_date, :servertime, '--look-for-date=PLACE', MAIL_DATE_PLACE_LIST,
|
234
|
+
"Choose the place (#{MAIL_DATE_PLACE_LIST.join(' ')}) to look for the date that as internaldate is appended with message. default is `servertime'."
|
235
|
+
]
|
236
|
+
]
|
237
|
+
|
238
|
+
def initialize(options, option_list)
|
239
|
+
@options = options
|
240
|
+
@option_list = option_list
|
241
|
+
@conf = {}
|
242
|
+
for key, value, *option_description in option_list
|
243
|
+
@conf[key] = value
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def [](key)
|
248
|
+
@conf[key]
|
249
|
+
end
|
250
|
+
|
251
|
+
def setup_option_list
|
252
|
+
@option_list.each do |key, value, *option_description|
|
253
|
+
@options.on(*option_description) do |v|
|
254
|
+
@conf[key] = v
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
self
|
259
|
+
end
|
260
|
+
|
261
|
+
def help_option(add_banner: nil)
|
262
|
+
@options.banner += add_banner if add_banner
|
263
|
+
@options.on('-h', '--help', 'Show this message.') do
|
264
|
+
puts @options
|
265
|
+
exit
|
266
|
+
end
|
267
|
+
|
268
|
+
self
|
269
|
+
end
|
270
|
+
|
271
|
+
def quiet_option(default_verbose: true)
|
272
|
+
@conf[:verbose] = default_verbose
|
273
|
+
@options.on('-v', '--[no-]verbose', 'Enable verbose messages. default is verbose.') do |verbose|
|
274
|
+
@conf[:verbose] = verbose
|
275
|
+
end
|
276
|
+
@options.on('-q', '--[no-]quiet', 'Disable verbose messages. default is verbose.') do |quiet|
|
277
|
+
@conf[:verbose] = ! quiet
|
278
|
+
end
|
279
|
+
|
280
|
+
self
|
281
|
+
end
|
282
|
+
|
283
|
+
def load_config_option
|
284
|
+
@options.on('-f', '--config-yaml=CONFIG_FILE',
|
285
|
+
"Load optional parameters from CONFIG_FILE.") do |path|
|
286
|
+
for name, value in YAML.load_file(path)
|
287
|
+
@conf[name.to_sym] = value
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
self
|
292
|
+
end
|
293
|
+
|
294
|
+
def load_library_option
|
295
|
+
@options.on('-r', '--load-library=LIBRARY', 'require LIBRARY.') do |library|
|
296
|
+
require(library)
|
297
|
+
end
|
298
|
+
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
def key_value_store_option
|
303
|
+
@conf[:key_value_store_type] = GDBM_KeyValueStore
|
304
|
+
@options.on('--kvs-type=TYPE', 'Choose the key-value store type.') do |kvs_type|
|
305
|
+
@conf[:key_value_store_type] = KeyValueStore::FactoryBuilder.get_plug_in(kvs_type)
|
306
|
+
end
|
307
|
+
|
308
|
+
@conf[:use_key_value_store_checksum] = true
|
309
|
+
@options.on('--[no-]use-kvs-cksum', 'Enable/disable data checksum at key-value store. default is enabled.') do |use_checksum|
|
310
|
+
@conf[:use_key_value_store_checksum] = use_checksum
|
311
|
+
end
|
312
|
+
|
313
|
+
self
|
314
|
+
end
|
315
|
+
|
316
|
+
def parse_options!(args, order: false)
|
317
|
+
if (order) then
|
318
|
+
@options.order!(args)
|
319
|
+
else
|
320
|
+
@options.parse!(args)
|
321
|
+
end
|
322
|
+
pp @conf if $DEBUG
|
323
|
+
|
324
|
+
self
|
325
|
+
end
|
326
|
+
|
327
|
+
def imap_debug_option
|
328
|
+
@options.on('--[no-]imap-debug',
|
329
|
+
"Set the debug flag of Net::IMAP class. default is false.") do |v|
|
330
|
+
Net::IMAP.debug = v
|
331
|
+
end
|
332
|
+
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
def imap_connect
|
337
|
+
unless (@conf[:username] && @conf[:password]) then
|
338
|
+
raise 'need for username and password.'
|
339
|
+
end
|
340
|
+
|
341
|
+
imap = Net::IMAP.new(@conf[:imap_host], port: @conf[:imap_port], ssl: @conf[:imap_ssl])
|
342
|
+
begin
|
343
|
+
if (@conf[:verbose]) then
|
344
|
+
puts "server greeting: #{imap_res2str(imap.greeting)}"
|
345
|
+
puts "server capability: #{imap.capability.join(' ')}"
|
346
|
+
end
|
347
|
+
|
348
|
+
case (@conf[:auth_type])
|
349
|
+
when 'login'
|
350
|
+
res = imap.login(@conf[:username], @conf[:password])
|
351
|
+
puts "login: #{imap_res2str(res)}" if @conf[:verbose]
|
352
|
+
when 'plain', 'cram-md5'
|
353
|
+
res = imap.authenticate(@conf[:auth_type], @conf[:username], @conf[:password])
|
354
|
+
puts "authenticate: #{imap_res2str(res)}" if @conf[:verbose]
|
355
|
+
else
|
356
|
+
raise "unknown authentication type: #{@conf[:auth_type]}"
|
357
|
+
end
|
358
|
+
|
359
|
+
yield(imap)
|
360
|
+
ensure
|
361
|
+
Error.suppress_2nd_error_at_resource_closing{ imap.logout }
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def make_imap_store_flags
|
366
|
+
store_flags = []
|
367
|
+
[ [ :store_flag_answered, :Answered ],
|
368
|
+
[ :store_flag_flagged, :Flagged ],
|
369
|
+
[ :store_flag_deleted, :Deleted ],
|
370
|
+
[ :store_flag_seen, :Seen ],
|
371
|
+
[ :store_flag_draft, :Draft ]
|
372
|
+
].each do |key, flag|
|
373
|
+
if (@conf[key]) then
|
374
|
+
store_flags << flag
|
375
|
+
end
|
376
|
+
end
|
377
|
+
puts "store flags: (#{store_flags.join(' ')})" if @conf[:verbose]
|
378
|
+
|
379
|
+
store_flags
|
380
|
+
end
|
381
|
+
|
382
|
+
def look_for_date(message_text, path=nil)
|
383
|
+
case (@conf[:look_for_date])
|
384
|
+
when :servertime
|
385
|
+
nil
|
386
|
+
when :localtime
|
387
|
+
Time.now
|
388
|
+
when :filetime
|
389
|
+
if (path) then
|
390
|
+
File.stat(path).mtime
|
391
|
+
end
|
392
|
+
when :mailheader
|
393
|
+
RFC822::Message.new(message_text).date
|
394
|
+
else
|
395
|
+
raise "failed to look for date: #{place}"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def make_kvs_factory
|
400
|
+
builder = KeyValueStore::FactoryBuilder.new
|
401
|
+
builder.open{|name| @conf[:key_value_store_type].open_with_conf(name, {}) }
|
402
|
+
if (@conf[:use_key_value_store_checksum]) then
|
403
|
+
builder.use(Checksum_KeyValueStore)
|
404
|
+
end
|
405
|
+
builder.factory
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def cmd_daemon(options, args)
|
410
|
+
conf = Config.new(options,
|
411
|
+
[ [ :is_daemon,
|
412
|
+
true,
|
413
|
+
'--[no-]daemon',
|
414
|
+
'Start daemon process. default is enabled.'
|
415
|
+
],
|
416
|
+
[ :is_syslog,
|
417
|
+
true,
|
418
|
+
'--[no-]syslog',
|
419
|
+
'Syslog daemon messages. default is enabled.'
|
420
|
+
]
|
421
|
+
])
|
422
|
+
conf.help_option(add_banner: ' start/stop/restart/status [server options]')
|
423
|
+
conf.quiet_option
|
424
|
+
conf.setup_option_list
|
425
|
+
conf.parse_options!(args, order: true)
|
426
|
+
pp args if $DEBUG
|
427
|
+
|
428
|
+
operation = args.shift or raise 'need for daemon operation.'
|
429
|
+
server_args = args.dup
|
430
|
+
server_options = OptionParser.new
|
431
|
+
server_conf = make_server_config(server_options)
|
432
|
+
server_options.parse!(server_args)
|
433
|
+
stat_file_path = Daemon.make_stat_file_path(server_conf.base_dir)
|
434
|
+
pp server_conf if $DEBUG
|
435
|
+
|
436
|
+
case (operation)
|
437
|
+
when 'start'
|
438
|
+
if (conf[:is_daemon]) then
|
439
|
+
args += %w[ --log-stdout=quiet ]
|
440
|
+
Process.daemon(true)
|
441
|
+
end
|
442
|
+
|
443
|
+
logger = Multiplexor.new
|
444
|
+
unless (conf[:is_daemon]) then
|
445
|
+
stdout_logger = Logger.new(STDOUT)
|
446
|
+
def stdout_logger.close # should not be closed at child process.
|
447
|
+
nil
|
448
|
+
end
|
449
|
+
logger.add(stdout_logger)
|
450
|
+
end
|
451
|
+
if (conf[:is_syslog]) then
|
452
|
+
syslog_logger = Syslog::Logger.new('rims-daemon')
|
453
|
+
def syslog_logger.close # should be closed at child process.
|
454
|
+
Syslog.close
|
455
|
+
end
|
456
|
+
logger.add(syslog_logger)
|
457
|
+
end
|
458
|
+
|
459
|
+
daemon = Daemon.new(stat_file_path, logger, server_options: args)
|
460
|
+
|
461
|
+
[ [ Daemon::RELOAD_SIGNAL_LIST, proc{ daemon.reload_server } ],
|
462
|
+
[ Daemon::RESTART_SIGNAL_LIST, proc{ daemon.restart_server } ],
|
463
|
+
[ Daemon::STOP_SIGNAL_LIST, proc{ daemon.stop_server } ]
|
464
|
+
].each do |signal_list, signal_command|
|
465
|
+
for sig_name in signal_list
|
466
|
+
Signal.trap(sig_name, signal_command)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
daemon.run
|
471
|
+
when 'stop'
|
472
|
+
stat_file = Daemon.new_status_file(stat_file_path)
|
473
|
+
stat_file.open{
|
474
|
+
stat_file.should_be_locked
|
475
|
+
pid = YAML.load(stat_file.read)['pid']
|
476
|
+
Process.kill(Daemon::STOP_SIGNAL, pid)
|
477
|
+
}
|
478
|
+
when 'restart'
|
479
|
+
stat_file = Daemon.new_status_file(stat_file_path)
|
480
|
+
stat_file.open{
|
481
|
+
stat_file.should_be_locked
|
482
|
+
pid = YAML.load(stat_file.read)['pid']
|
483
|
+
Process.kill(Daemon::RESTART_SIGNAL, pid)
|
484
|
+
}
|
485
|
+
when 'status'
|
486
|
+
stat_file = Daemon.new_status_file(stat_file_path)
|
487
|
+
stat_file.open{
|
488
|
+
if (stat_file.locked?) then
|
489
|
+
puts 'daemon is running.' if conf[:verbose]
|
490
|
+
return 0
|
491
|
+
else
|
492
|
+
puts 'daemon is stopped.' if conf[:verbose]
|
493
|
+
return 1
|
494
|
+
end
|
495
|
+
}
|
496
|
+
else
|
497
|
+
raise "unknown daemon operation: #{operation}"
|
498
|
+
end
|
499
|
+
|
500
|
+
0
|
501
|
+
end
|
502
|
+
command_function :cmd_daemon, "Daemon start/stop/status tool."
|
503
|
+
|
504
|
+
def imap_append(imap, mailbox, message, store_flags: [], date_time: nil, verbose: false)
|
505
|
+
puts "message date: #{date_time}" if (verbose && date_time)
|
506
|
+
store_flags = nil if store_flags.empty?
|
507
|
+
res = imap.append(mailbox, message, store_flags, date_time)
|
508
|
+
puts "append: #{imap_res2str(res)}" if verbose
|
509
|
+
nil
|
510
|
+
end
|
511
|
+
module_function :imap_append
|
512
|
+
|
513
|
+
def each_message(args, verbose: false)
|
514
|
+
if (args.empty?) then
|
515
|
+
msg_txt = STDIN.read
|
516
|
+
yield(msg_txt)
|
517
|
+
return 0
|
518
|
+
else
|
519
|
+
error_count = 0
|
520
|
+
args.each_with_index do |filename, i|
|
521
|
+
puts "progress: #{i + 1}/#{args.length}" if verbose
|
522
|
+
begin
|
523
|
+
msg_txt = IO.read(filename, mode: 'rb', encoding: 'ascii-8bit')
|
524
|
+
yield(msg_txt)
|
525
|
+
rescue
|
526
|
+
error_count += 1
|
527
|
+
puts "failed to append message: #{filename}"
|
528
|
+
puts "error: #{$!}"
|
529
|
+
if ($DEBUG) then
|
530
|
+
for frame in $!.backtrace
|
531
|
+
puts frame
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
if (error_count > 0) then
|
538
|
+
puts "#{error_count} errors!"
|
539
|
+
return 1
|
540
|
+
else
|
541
|
+
return 0
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
module_function :each_message
|
546
|
+
|
547
|
+
def cmd_post_mail(options, args)
|
548
|
+
STDIN.set_encoding(Encoding::ASCII_8BIT)
|
549
|
+
|
550
|
+
option_list =
|
551
|
+
Config::VERBOSE_OPTION_LIST +
|
552
|
+
Config::POST_MAIL_CONNECT_OPTION_LIST +
|
553
|
+
Config::IMAP_MAILBOX_OPTION_LIST +
|
554
|
+
Config::IMAP_STORE_FLAG_OPTION_LIST +
|
555
|
+
Config::MAIL_DATE_OPTION_LIST
|
556
|
+
|
557
|
+
conf = Config.new(options, option_list)
|
558
|
+
conf.help_option(add_banner: ' [POST USER] [MESSAGE_FILEs]')
|
559
|
+
conf.load_config_option
|
560
|
+
conf.setup_option_list
|
561
|
+
conf.imap_debug_option
|
562
|
+
conf.parse_options!(args)
|
563
|
+
|
564
|
+
post_user = args.shift or raise 'need for post user.'
|
565
|
+
|
566
|
+
store_flags = conf.make_imap_store_flags
|
567
|
+
conf.imap_connect{|imap|
|
568
|
+
unless (imap.capability.find{|c| c == 'X-RIMS-MAIL-DELIVERY-USER' }) then
|
569
|
+
warn('warning: This IMAP server might not support RIMS mail delivery protocol.')
|
570
|
+
end
|
571
|
+
each_message(args) do |msg_txt|
|
572
|
+
t = conf.look_for_date(msg_txt)
|
573
|
+
encoded_mbox_name = Protocol::Decoder.encode_delivery_target_mailbox(post_user, conf[:mailbox])
|
574
|
+
imap_append(imap, encoded_mbox_name, msg_txt, store_flags: store_flags, date_time: t, verbose: conf[:verbose])
|
575
|
+
end
|
576
|
+
}
|
577
|
+
end
|
578
|
+
command_function :cmd_post_mail, "Post mail to any user."
|
579
|
+
|
580
|
+
def cmd_imap_append(options, args)
|
581
|
+
STDIN.set_encoding(Encoding::ASCII_8BIT)
|
582
|
+
|
583
|
+
option_list =
|
584
|
+
Config::VERBOSE_OPTION_LIST +
|
585
|
+
Config::IMAP_CONNECT_OPTION_LIST +
|
586
|
+
Config::IMAP_MAILBOX_OPTION_LIST +
|
587
|
+
Config::IMAP_STORE_FLAG_OPTION_LIST +
|
588
|
+
Config::MAIL_DATE_OPTION_LIST
|
589
|
+
|
590
|
+
conf = Config.new(options, option_list)
|
591
|
+
conf.help_option(add_banner: ' [MESSAGE_FILEs]')
|
592
|
+
conf.load_config_option
|
593
|
+
conf.setup_option_list
|
594
|
+
conf.imap_debug_option
|
595
|
+
conf.parse_options!(args)
|
596
|
+
|
597
|
+
store_flags = conf.make_imap_store_flags
|
598
|
+
conf.imap_connect{|imap|
|
599
|
+
each_message(args) do |msg_txt|
|
600
|
+
t = conf.look_for_date(msg_txt)
|
601
|
+
imap_append(imap, conf[:mailbox], msg_txt, store_flags: store_flags, date_time: t, verbose: conf[:verbose])
|
602
|
+
end
|
603
|
+
}
|
604
|
+
end
|
605
|
+
command_function :cmd_imap_append, "Append message to IMAP mailbox."
|
606
|
+
|
607
|
+
def cmd_mbox_dirty_flag(options, args)
|
608
|
+
option_list = [
|
609
|
+
[ :return_flag_exit_code, true, '--[no-]return-flag-exit-code', 'Dirty flag value is returned to exit code. default is true.' ]
|
610
|
+
]
|
611
|
+
|
612
|
+
conf = Config.new(options, option_list)
|
613
|
+
conf.load_library_option
|
614
|
+
conf.key_value_store_option
|
615
|
+
conf.help_option(add_banner: ' [mailbox directory]')
|
616
|
+
conf.quiet_option
|
617
|
+
conf.setup_option_list
|
618
|
+
|
619
|
+
write_dirty_flag = nil
|
620
|
+
options.on('--enable-dirty-flag', 'Enable mailbox dirty flag.') { write_dirty_flag = true }
|
621
|
+
options.on('--disable-dirty-flag', 'Disable mailbox dirty flag.') { write_dirty_flag = false }
|
622
|
+
|
623
|
+
conf.parse_options!(args)
|
624
|
+
pp conf if $DEBUG
|
625
|
+
|
626
|
+
mbox_dir = args.shift or raise 'need for mailbox directory.'
|
627
|
+
meta_db_path = File.join(mbox_dir, 'meta')
|
628
|
+
unless (conf[:key_value_store_type].exist? meta_db_path) then
|
629
|
+
raise "not found a mailbox meta DB: #{meta_db_path}"
|
630
|
+
end
|
631
|
+
|
632
|
+
kvs_factory = conf.make_kvs_factory
|
633
|
+
meta_db = DB::Meta.new(kvs_factory.call(File.join(mbox_dir, 'meta')))
|
634
|
+
begin
|
635
|
+
unless (write_dirty_flag.nil?) then
|
636
|
+
meta_db.dirty = write_dirty_flag
|
637
|
+
end
|
638
|
+
|
639
|
+
if (conf[:verbose]) then
|
640
|
+
puts "dirty flag is #{meta_db.dirty?}."
|
641
|
+
end
|
642
|
+
|
643
|
+
if (conf[:return_flag_exit_code]) then
|
644
|
+
if (meta_db.dirty?) then
|
645
|
+
1
|
646
|
+
else
|
647
|
+
0
|
648
|
+
end
|
649
|
+
else
|
650
|
+
0
|
651
|
+
end
|
652
|
+
ensure
|
653
|
+
Error.suppress_2nd_error_at_resource_closing{ meta_db.close }
|
654
|
+
end
|
655
|
+
end
|
656
|
+
command_function :cmd_mbox_dirty_flag, 'Show/enable/disable dirty flag of mailbox database.'
|
657
|
+
|
658
|
+
def cmd_unique_user_id(options, args)
|
659
|
+
options.banner += ' [username]'
|
660
|
+
options.parse!(args)
|
661
|
+
|
662
|
+
if (args.length != 1) then
|
663
|
+
raise 'need for a username.'
|
664
|
+
end
|
665
|
+
username = args.shift
|
666
|
+
|
667
|
+
puts Authentication.unique_user_id(username)
|
668
|
+
|
669
|
+
0
|
670
|
+
end
|
671
|
+
command_function :cmd_unique_user_id, 'Show unique user ID from username.'
|
672
|
+
|
673
|
+
def cmd_show_user_mbox(options, args)
|
674
|
+
conf = RIMS::Config.new
|
675
|
+
load_server_config = false
|
676
|
+
|
677
|
+
options.banner += ' [base directory] [username] OR -f [config.yml path] [username]'
|
678
|
+
options.on('-f', '--config-yaml=CONFIG_FILE',
|
679
|
+
'Load optional parameters from CONFIG_FILE.') do |path|
|
680
|
+
conf.load_config_yaml(path)
|
681
|
+
load_server_config = true
|
682
|
+
end
|
683
|
+
options.parse!(args)
|
684
|
+
|
685
|
+
unless (load_server_config) then
|
686
|
+
base_dir = args.shift or raise 'need for base directory.'
|
687
|
+
conf.load(base_dir: base_dir)
|
688
|
+
end
|
689
|
+
|
690
|
+
username = args.shift or raise 'need for a username.'
|
691
|
+
unique_user_id = Authentication.unique_user_id(username)
|
692
|
+
puts conf.make_key_value_store_path_from_base_dir(MAILBOX_DATA_STRUCTURE_VERSION, unique_user_id)
|
693
|
+
|
694
|
+
0
|
695
|
+
end
|
696
|
+
command_function :cmd_show_user_mbox, "Show the path in which user's mailbox data is stored."
|
697
|
+
|
698
|
+
def cmd_pass_hash(options, args)
|
699
|
+
option_list = [
|
700
|
+
[ :hash_type, 'SHA256', '--hash-type=DIGEST', 'Password hash type (ex SHA256, MD5, etc). default is SHA256.' ],
|
701
|
+
[ :stretch_count, 10000, '--stretch-count=COUNT', Integer, 'Count to stretch password hash. default is 10000.' ],
|
702
|
+
[ :salt_size, 16, '--salt-size=OCTETS', Integer, 'Size of salt string. default is 16 octets.' ]
|
703
|
+
]
|
704
|
+
|
705
|
+
conf = Config.new(options, option_list)
|
706
|
+
conf.help_option(add_banner: <<-'EOF'.chomp)
|
707
|
+
passwd_plain.yml
|
708
|
+
Example
|
709
|
+
$ cat passwd_plain.yml
|
710
|
+
- { user: foo, pass: open_sesame }
|
711
|
+
- { user: "#postman", pass: "#postman" }
|
712
|
+
$ rims pass-hash passwd_plain.yml >passwd_hash.yml
|
713
|
+
$ cat passwd_hash.yml
|
714
|
+
---
|
715
|
+
- user: foo
|
716
|
+
hash: SHA256:10000:YkslZucwN2QJ7LOft59Pgw==:d5dca9109cc787220eba65810e40165079ce3292407e74e8fbd5c6a8a9b12204
|
717
|
+
- user: "#postman"
|
718
|
+
hash: SHA256:10000:6Qj/wAYmb7NUGdOy0N35qg==:e967e46b8e0d9df6324e66c7e42da64911a8715e06a123fe5abf7af4ca45a386
|
719
|
+
Options:
|
720
|
+
EOF
|
721
|
+
conf.setup_option_list
|
722
|
+
conf.parse_options!(args)
|
723
|
+
pp conf if $DEBUG
|
724
|
+
|
725
|
+
case (args.length)
|
726
|
+
when 0
|
727
|
+
passwd, *optional = YAML.load_stream(STDIN)
|
728
|
+
when 1
|
729
|
+
passwd, *optional = File.open(args[0]) {|f| YAML.load_stream(f) }
|
730
|
+
else
|
731
|
+
raise ArgumentError, 'too many input files.'
|
732
|
+
end
|
733
|
+
|
734
|
+
digest_factory = Password::HashSource.search_digest_factory(conf[:hash_type])
|
735
|
+
salt_generator = Password::HashSource.make_salt_generator(conf[:salt_size])
|
736
|
+
|
737
|
+
for entry in passwd
|
738
|
+
pass = entry.delete('pass') or raise "not found a `pass' entry."
|
739
|
+
entry['hash'] = Password::HashSource.make_entry(digest_factory, conf[:stretch_count], salt_generator.call, pass).to_s
|
740
|
+
end
|
741
|
+
|
742
|
+
puts passwd.to_yaml
|
743
|
+
|
744
|
+
0
|
745
|
+
end
|
746
|
+
command_function :cmd_pass_hash, 'Make hash password configuration file from plain password configuration file.'
|
747
|
+
|
748
|
+
def cmd_debug_dump_kvs(options, args)
|
749
|
+
option_list = [
|
750
|
+
[ :match_key, nil, '--match-key=REGEXP', Regexp, 'Show keys matching regular expression.' ],
|
751
|
+
[ :dump_size, true, '--[no-]dump-size', 'Dump size of value with key.' ],
|
752
|
+
[ :dump_value, true, '--[no-]dump-value', 'Dump value with key.' ],
|
753
|
+
[ :marshal_restore, true, '--[no-]marshal-restore', 'Restore serialized object.' ]
|
754
|
+
]
|
755
|
+
|
756
|
+
conf = Config.new(options, option_list)
|
757
|
+
conf.load_library_option
|
758
|
+
conf.key_value_store_option
|
759
|
+
conf.help_option(add_banner: ' [DB_NAME]')
|
760
|
+
conf.setup_option_list
|
761
|
+
conf.parse_options!(args)
|
762
|
+
pp conf if $DEBUG
|
763
|
+
|
764
|
+
name = args.shift or raise 'need for DB name.'
|
765
|
+
unless (conf[:key_value_store_type].exist? name) then
|
766
|
+
raise "not found a key-value store: #{name}"
|
767
|
+
end
|
768
|
+
|
769
|
+
factory = conf.make_kvs_factory
|
770
|
+
db = factory.call(name)
|
771
|
+
begin
|
772
|
+
db.each_key do |key|
|
773
|
+
if (conf[:match_key] && (key !~ conf[:match_key])) then
|
774
|
+
next
|
775
|
+
end
|
776
|
+
|
777
|
+
entry = key.inspect
|
778
|
+
if (conf[:dump_size]) then
|
779
|
+
size = db[key].bytesize
|
780
|
+
entry += ": #{size} bytes"
|
781
|
+
end
|
782
|
+
if (conf[:dump_value]) then
|
783
|
+
v = db[key]
|
784
|
+
if (conf[:marshal_restore]) then
|
785
|
+
begin
|
786
|
+
v = Marshal.restore(v)
|
787
|
+
rescue
|
788
|
+
# not marshal object!
|
789
|
+
end
|
790
|
+
end
|
791
|
+
entry += ": #{v.inspect}"
|
792
|
+
end
|
793
|
+
|
794
|
+
puts entry
|
795
|
+
end
|
796
|
+
ensure
|
797
|
+
Error.suppress_2nd_error_at_resource_closing{ db.close }
|
798
|
+
end
|
799
|
+
|
800
|
+
0
|
801
|
+
end
|
802
|
+
command_function :cmd_debug_dump_kvs, "Dump key-value store contents."
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
# Local Variables:
|
807
|
+
# mode: Ruby
|
808
|
+
# indent-tabs-mode: nil
|
809
|
+
# End:
|