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,251 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'digest'
4
+ require 'securerandom'
5
+
6
+ module RIMS
7
+ module Password
8
+ # expected behavior of a typical password source.
9
+ # 1. a password source plug-in file is loaded by a
10
+ # configuration file item of <tt>load_libraries</tt>.
11
+ # (exceptions: RIMS::Password::PlainSource and
12
+ # RIMS::Password::HashSource are not need to load.)
13
+ # 2. RIMS::Authentication.add_plug_in is called to enable a
14
+ # password source on loading its plug-in file.
15
+ # 3. a password source object is built from a configuration
16
+ # file by its <tt>build_from_conf</tt> class method.
17
+ # 4. if any password source's <tt>raw_passwrd?</tt> method
18
+ # returns a <tt>false</tt> value, RIMS IMAP server disables
19
+ # challenge-response authentication options (ex. CRAM-MD5).
20
+ # 5. a logger object is set to a <tt>logger</tt> write
21
+ # attribute on a password source object. and the password
22
+ # source's <tt>start</tt> method is called.
23
+ # 6. a password source object authenticates a user by its
24
+ # authentication methods those are <tt>fetch_password</tt>
25
+ # or <tt>compare_password</tt>.
26
+ # 7. to deliver a message to a user, a password source object
27
+ # confirms existence of a user by its <tt>user?</tt>
28
+ # method.
29
+ # 8. a <tt>stop</tt> method of a password source object is
30
+ # called at stopping server.
31
+ class Source
32
+ attr_writer :logger
33
+
34
+ def start
35
+ end
36
+
37
+ def stop
38
+ end
39
+
40
+ # this method declares that this password source returns a plain
41
+ # text password or do not it.
42
+ # if this method will return a <tt>true</tt> value,
43
+ # <tt>fetch_password</tt> may be called.
44
+ # if this method will return a <tt>false</tt> value,
45
+ # <tt>fetch_password</tt> will never be called.
46
+ def raw_password?
47
+ false
48
+ end
49
+
50
+ def user?(username)
51
+ raise NotImplementedError, 'not implemented.'
52
+ end
53
+
54
+ # if a user exists, this method should return the user's plain
55
+ # text password.
56
+ # if a user does not exist, this method should return a
57
+ # <tt>nil</tt> value.
58
+ def fetch_password(username)
59
+ nil
60
+ end
61
+
62
+ # if a user exists and the user's password is right, this method
63
+ # should return a true context value (not <tt>false</tt>,
64
+ # <tt>nil</tt>).
65
+ # if a user exists and the user's password is wrong, this method
66
+ # should return a false context value (<tt>false<</tt> or
67
+ # <tt>nil</tt>).
68
+ # if a user does not exist, this method should return a false
69
+ # context value (<tt>false<</tt> or <tt>nil</tt>).
70
+ def compare_password(username, password)
71
+ if (raw_password = fetch_password(username)) then
72
+ password == raw_password
73
+ end
74
+ end
75
+
76
+ def self.build_from_conf(config)
77
+ raise NotImplementedError, 'not implemented.'
78
+ end
79
+ end
80
+
81
+ class PlainSource < Source
82
+ def initialize
83
+ @passwd = {}
84
+ end
85
+
86
+ def start
87
+ if (@logger.debug?) then
88
+ @passwd.each_key do |name|
89
+ @logger.debug("user name: #{name}")
90
+ end
91
+ end
92
+ nil
93
+ end
94
+
95
+ def stop
96
+ @passwd.clear
97
+ nil
98
+ end
99
+
100
+ def raw_password?
101
+ true
102
+ end
103
+
104
+ def entry(username, password)
105
+ @passwd[username] = password
106
+ self
107
+ end
108
+
109
+ def user?(username)
110
+ @passwd.key? username
111
+ end
112
+
113
+ def fetch_password(username)
114
+ @passwd[username]
115
+ end
116
+
117
+ def self.build_from_conf(config)
118
+ plain_src = self.new
119
+ for user_entry in config
120
+ plain_src.entry(user_entry['user'], user_entry['pass'])
121
+ end
122
+
123
+ plain_src
124
+ end
125
+ end
126
+ Authentication.add_plug_in('plain', PlainSource)
127
+
128
+ class HashSource < Source
129
+ class Entry
130
+ def self.encode(digest, stretch_count, salt, password)
131
+ salt_password = salt.b + password.b
132
+ digest.update(salt_password)
133
+ stretch_count.times do
134
+ digest.update(digest.digest + salt_password)
135
+ end
136
+ digest.hexdigest
137
+ end
138
+
139
+ def initialize(digest_factory, stretch_count, salt, hash)
140
+ @digest_factory = digest_factory
141
+ @stretch_count = stretch_count
142
+ @salt = salt
143
+ @hash = hash
144
+ end
145
+
146
+ def hash_type
147
+ @digest_factory.to_s.sub(/^Digest::/, '')
148
+ end
149
+
150
+ attr_reader :stretch_count
151
+ attr_reader :salt
152
+ attr_reader :hash
153
+
154
+ def salt_base64
155
+ Protocol.encode_base64(@salt)
156
+ end
157
+
158
+ def to_s
159
+ [ hash_type, @stretch_count, salt_base64, @hash ].join(':')
160
+ end
161
+
162
+ def compare(password)
163
+ self.class.encode(@digest_factory.new, @stretch_count, @salt, password) == @hash
164
+ end
165
+ end
166
+
167
+ def self.search_digest_factory(hash_type)
168
+ if (digest_factory = Digest.const_get(hash_type)) then
169
+ if (digest_factory < Digest::Base) then
170
+ return digest_factory
171
+ end
172
+ end
173
+ raise TypeError, "not a digest factory: #{hash_type}"
174
+ end
175
+
176
+ def self.make_salt_generator(octets)
177
+ proc{ SecureRandom.random_bytes(octets) }
178
+ end
179
+
180
+ def self.make_entry(digest_factory, stretch_count, salt, password)
181
+ hash = Entry.encode(digest_factory.new, stretch_count, salt, password)
182
+ Entry.new(digest_factory, stretch_count, salt, hash)
183
+ end
184
+
185
+ # hash password format:
186
+ # [hash type]:[stretch count]:[base64 encoded salt]:[password hash hex digest]
187
+ # example:
188
+ # SHA256:1000:2tImt4kLqLM=:756f633bf70613555aa93a5be1e5d93adfe87160e794abc6294c3b58a18f93aa
189
+ def self.parse_entry(password_hash)
190
+ hash_type, stretch_count, salt_base64, hash = password_hash.split(':', 4)
191
+ digest_factory = search_digest_factory(hash_type)
192
+ stretch_count = stretch_count.to_i
193
+ salt = Protocol.decode_base64(salt_base64)
194
+ Entry.new(digest_factory, stretch_count, salt, hash)
195
+ end
196
+
197
+ def initialize
198
+ @passwd = {}
199
+ end
200
+
201
+ def start
202
+ if (@logger.debug?) then
203
+ for name, entry in @passwd
204
+ @logger.debug("user name: #{name}")
205
+ @logger.debug("password hash: #{entry}")
206
+ end
207
+ end
208
+ nil
209
+ end
210
+
211
+ def stop
212
+ @passwd.clear
213
+ nil
214
+ end
215
+
216
+ def raw_password?
217
+ false
218
+ end
219
+
220
+ def add(username, entry)
221
+ @passwd[username] = entry
222
+ self
223
+ end
224
+
225
+ def user?(username)
226
+ @passwd.key? username
227
+ end
228
+
229
+ def compare_password(username, password)
230
+ if (entry = @passwd[username]) then
231
+ entry.compare(password)
232
+ end
233
+ end
234
+
235
+ def self.build_from_conf(config)
236
+ hash_src = self.new
237
+ for user_entry in config
238
+ hash_src.add(user_entry['user'], parse_entry(user_entry['hash']))
239
+ end
240
+
241
+ hash_src
242
+ end
243
+ end
244
+ Authentication.add_plug_in('hash', HashSource)
245
+ end
246
+ end
247
+
248
+ # Local Variables:
249
+ # mode: Ruby
250
+ # indent-tabs-mode: nil
251
+ # End:
@@ -0,0 +1,88 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module RIMS
4
+ class ObjectPool
5
+ class ObjectHolder
6
+ def initialize(object_pool, object_key)
7
+ @object_pool = object_pool
8
+ @object_key = object_key
9
+ end
10
+
11
+ attr_reader :object_key
12
+
13
+ def object_destroy
14
+ end
15
+
16
+ # optional block is called when a mail store is closed.
17
+ def return_pool(**name_args, &block) # yields:
18
+ @object_pool.put(self, **name_args, &block)
19
+ nil
20
+ end
21
+ end
22
+
23
+ class ReferenceCount
24
+ def initialize(count, object_holder)
25
+ @count = count
26
+ @object_holder = object_holder
27
+ end
28
+
29
+ attr_accessor :count
30
+ attr_reader :object_holder
31
+
32
+ def object_destroy
33
+ @object_holder.object_destroy
34
+ end
35
+ end
36
+
37
+ def initialize(&object_factory) # yields: object_pool, object_key, object_lock
38
+ @object_factory = object_factory
39
+ @pool_map = {}
40
+ @pool_lock = Mutex.new
41
+ @object_lock_map = Hash.new{|hash, key| hash[key] = ReadWriteLock.new }
42
+ end
43
+
44
+ def empty?
45
+ @pool_map.empty?
46
+ end
47
+
48
+ # optional block is called when a new object is added to an object pool.
49
+ def get(object_key, timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS) # yields:
50
+ object_lock = @pool_lock.synchronize{ @object_lock_map[object_key] }
51
+ object_lock.write_synchronize(timeout_seconds) {
52
+ if (@pool_lock.synchronize{ @pool_map.key? object_key }) then
53
+ ref_count = @pool_lock.synchronize{ @pool_map[object_key] }
54
+ else
55
+ yield if block_given?
56
+ object_holder = @object_factory.call(self, object_key, object_lock)
57
+ ref_count = ReferenceCount.new(0, object_holder)
58
+ @pool_lock.synchronize{ @pool_map[object_key] = ref_count }
59
+ end
60
+ ref_count.count >= 0 or raise 'internal error'
61
+ ref_count.count += 1
62
+ ref_count.object_holder
63
+ }
64
+ end
65
+
66
+ # optional block is called when an object is deleted from an object pool.
67
+ def put(object_holder, timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS) # yields:
68
+ object_lock = @pool_lock.synchronize{ @object_lock_map[object_holder.object_key] }
69
+ object_lock.write_synchronize(timeout_seconds) {
70
+ ref_count = @pool_lock.synchronize{ @pool_map[object_holder.object_key] } or raise 'internal error'
71
+ ref_count.object_holder.equal? object_holder or raise 'internal error'
72
+ ref_count.count > 0 or raise 'internal error'
73
+ ref_count.count -= 1
74
+ if (ref_count.count == 0) then
75
+ @pool_lock.synchronize{ @pool_map.delete(object_holder.object_key) }
76
+ ref_count.object_destroy
77
+ yield if block_given?
78
+ end
79
+ }
80
+ nil
81
+ end
82
+ end
83
+ end
84
+
85
+ # Local Variables:
86
+ # mode: Ruby
87
+ # indent-tabs-mode: nil
88
+ # End:
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module RIMS
4
+ class ProtocolError < Error
5
+ end
6
+
7
+ class SyntaxError < ProtocolError
8
+ end
9
+
10
+ class MessageSetSyntaxError < SyntaxError
11
+ end
12
+
13
+ module Protocol
14
+ def quote(s)
15
+ qs = ''.encode(s.encoding)
16
+ case (s)
17
+ when /"/, /\n/
18
+ qs << '{' << s.bytesize.to_s << "}\r\n" << s
19
+ else
20
+ qs << '"' << s << '"'
21
+ end
22
+ end
23
+ module_function :quote
24
+
25
+ def compile_wildcard(pattern)
26
+ src = '\A'
27
+ src << pattern.gsub(/.*?[*%]/) {|s| Regexp.quote(s[0..-2]) + '.*' }
28
+ src << Regexp.quote($') if $'
29
+ src << '\z'
30
+ Regexp.compile(src)
31
+ end
32
+ module_function :compile_wildcard
33
+
34
+ def io_data_log(str)
35
+ s = '<'
36
+ s << str.encoding.to_s
37
+ if (str.ascii_only?) then
38
+ s << ':ascii_only'
39
+ end
40
+ s << '> ' << str.inspect
41
+ end
42
+ module_function :io_data_log
43
+
44
+ def encode_base64(plain_txt)
45
+ [ plain_txt ].pack('m').each_line.map{|line| line.strip }.join('')
46
+ end
47
+ module_function :encode_base64
48
+
49
+ def decode_base64(base64_txt)
50
+ base64_txt.unpack('m')[0]
51
+ end
52
+ module_function :decode_base64
53
+
54
+ autoload :FetchBody, 'rims/protocol/parser'
55
+ autoload :RequestReader, 'rims/protocol/parser'
56
+ autoload :AuthenticationReader, 'rims/protocol/parser'
57
+ autoload :SearchParser, 'rims/protocol/parser'
58
+ autoload :FetchParser, 'rims/protocol/parser'
59
+ autoload :Decoder, 'rims/protocol/decoder'
60
+
61
+ def body(symbol: nil, option: nil, section: nil, section_list: nil, partial_origin: nil, partial_size: nil)
62
+ FetchBody.new(symbol, option, section, section_list, partial_origin, partial_size)
63
+ end
64
+ module_function :body
65
+ end
66
+ end
67
+
68
+ # Local Variables:
69
+ # mode: Ruby
70
+ # indent-tabs-mode: nil
71
+ # End:
@@ -0,0 +1,1469 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'logger'
4
+ require 'net/imap'
5
+ require 'time'
6
+
7
+ module RIMS
8
+ module Protocol
9
+ class Decoder
10
+ def self.new_decoder(*args, **opts)
11
+ InitialDecoder.new(*args, **opts)
12
+ end
13
+
14
+ def self.repl(decoder, input, output, logger)
15
+ response_write = proc{|res|
16
+ begin
17
+ last_line = nil
18
+ for data in res
19
+ logger.debug("response data: #{Protocol.io_data_log(data)}") if logger.debug?
20
+ output << data
21
+ last_line = data
22
+ end
23
+ output.flush
24
+ logger.info("server response: #{last_line.strip}")
25
+ rescue
26
+ logger.error('response write error.')
27
+ logger.error($!)
28
+ raise
29
+ end
30
+ }
31
+
32
+ decoder.ok_greeting{|res| response_write.call(res) }
33
+
34
+ request_reader = Protocol::RequestReader.new(input, output, logger)
35
+ loop do
36
+ begin
37
+ atom_list = request_reader.read_command
38
+ rescue
39
+ logger.error('invalid client command.')
40
+ logger.error($!)
41
+ response_write.call([ "* BAD client command syntax error\r\n" ])
42
+ next
43
+ end
44
+
45
+ break unless atom_list
46
+
47
+ tag, command, *opt_args = atom_list
48
+ logger.info("client command: #{tag} #{command}")
49
+ logger.debug("client command parameter: #{opt_args.inspect}") if logger.debug?
50
+
51
+ begin
52
+ case (command.upcase)
53
+ when 'CAPABILITY'
54
+ decoder.capability(tag, *opt_args) {|res| response_write.call(res) }
55
+ when 'NOOP'
56
+ decoder.noop(tag, *opt_args) {|res| response_write.call(res) }
57
+ when 'LOGOUT'
58
+ decoder.logout(tag, *opt_args) {|res| response_write.call(res) }
59
+ when 'AUTHENTICATE'
60
+ decoder.authenticate(tag, input, output, *opt_args) {|res| response_write.call(res) }
61
+ when 'LOGIN'
62
+ decoder.login(tag, *opt_args) {|res| response_write.call(res) }
63
+ when 'SELECT'
64
+ decoder.select(tag, *opt_args) {|res| response_write.call(res) }
65
+ when 'EXAMINE'
66
+ decoder.examine(tag, *opt_args) {|res| response_write.call(res) }
67
+ when 'CREATE'
68
+ decoder.create(tag, *opt_args) {|res| response_write.call(res) }
69
+ when 'DELETE'
70
+ decoder.delete(tag, *opt_args) {|res| response_write.call(res) }
71
+ when 'RENAME'
72
+ decoder.rename(tag, *opt_args) {|res| response_write.call(res) }
73
+ when 'SUBSCRIBE'
74
+ decoder.subscribe(tag, *opt_args) {|res| response_write.call(res) }
75
+ when 'UNSUBSCRIBE'
76
+ decoder.unsubscribe(tag, *opt_args) {|res| response_write.call(res) }
77
+ when 'LIST'
78
+ decoder.list(tag, *opt_args) {|res| response_write.call(res) }
79
+ when 'LSUB'
80
+ decoder.lsub(tag, *opt_args) {|res| response_write.call(res) }
81
+ when 'STATUS'
82
+ decoder.status(tag, *opt_args) {|res| response_write.call(res) }
83
+ when 'APPEND'
84
+ decoder.append(tag, *opt_args) {|res| response_write.call(res) }
85
+ when 'CHECK'
86
+ decoder.check(tag, *opt_args) {|res| response_write.call(res) }
87
+ when 'CLOSE'
88
+ decoder.close(tag, *opt_args) {|res| response_write.call(res) }
89
+ when 'EXPUNGE'
90
+ decoder.expunge(tag, *opt_args) {|res| response_write.call(res) }
91
+ when 'SEARCH'
92
+ decoder.search(tag, *opt_args) {|res| response_write.call(res) }
93
+ when 'FETCH'
94
+ decoder.fetch(tag, *opt_args) {|res| response_write.call(res) }
95
+ when 'STORE'
96
+ decoder.store(tag, *opt_args) {|res| response_write.call(res) }
97
+ when 'COPY'
98
+ decoder.copy(tag, *opt_args) {|res| response_write.call(res) }
99
+ when 'IDLE'
100
+ decoder.idle(tag, input, output, *opt_args) {|res| response_write.call(res) }
101
+ when 'UID'
102
+ unless (opt_args.empty?) then
103
+ uid_command, *uid_args = opt_args
104
+ logger.info("uid command: #{uid_command}")
105
+ logger.debug("uid parameter: #{uid_args}") if logger.debug?
106
+ case (uid_command.upcase)
107
+ when 'SEARCH'
108
+ decoder.search(tag, *uid_args, uid: true) {|res| response_write.call(res) }
109
+ when 'FETCH'
110
+ decoder.fetch(tag, *uid_args, uid: true) {|res| response_write.call(res) }
111
+ when 'STORE'
112
+ decoder.store(tag, *uid_args, uid: true) {|res| response_write.call(res) }
113
+ when 'COPY'
114
+ decoder.copy(tag, *uid_args, uid: true) {|res| response_write.call(res) }
115
+ else
116
+ logger.error("unknown uid command: #{uid_command}")
117
+ response_write.call([ "#{tag} BAD unknown uid command\r\n" ])
118
+ end
119
+ else
120
+ logger.error('empty uid parameter.')
121
+ response_write.call([ "#{tag} BAD empty uid parameter\r\n" ])
122
+ end
123
+ else
124
+ logger.error("unknown command: #{command}")
125
+ response_write.call([ "#{tag} BAD unknown command\r\n" ])
126
+ end
127
+ rescue
128
+ logger.error('unexpected error.')
129
+ logger.error($!)
130
+ response_write.call([ "#{tag} BAD unexpected error\r\n" ])
131
+ end
132
+
133
+ if (command.upcase == 'LOGOUT') then
134
+ break
135
+ end
136
+
137
+ decoder = decoder.next_decoder
138
+ end
139
+
140
+ nil
141
+ ensure
142
+ Error.suppress_2nd_error_at_resource_closing(logger: logger) { decoder.cleanup }
143
+ end
144
+
145
+ def initialize(auth, logger)
146
+ @auth = auth
147
+ @logger = logger
148
+ end
149
+
150
+ def response_stream(tag)
151
+ Enumerator.new{|res|
152
+ begin
153
+ yield(res)
154
+ rescue SyntaxError
155
+ @logger.error('client command syntax error.')
156
+ @logger.error($!)
157
+ res << "#{tag} BAD client command syntax error\r\n"
158
+ rescue
159
+ raise if ($!.name =~ /AssertionFailedError/)
160
+ @logger.error('internal server error.')
161
+ @logger.error($!)
162
+ res << "#{tag} BAD internal server error\r\n"
163
+ end
164
+ }
165
+ end
166
+ private :response_stream
167
+
168
+ def guard_error(tag, imap_command, *args, **name_args)
169
+ begin
170
+ if (name_args.empty?) then
171
+ __send__(imap_command, tag, *args) {|res| yield(res) }
172
+ else
173
+ __send__(imap_command, tag, *args, **name_args) {|res| yield(res) }
174
+ end
175
+ rescue SyntaxError
176
+ @logger.error('client command syntax error.')
177
+ @logger.error($!)
178
+ yield([ "#{tag} BAD client command syntax error\r\n" ])
179
+ rescue ArgumentError
180
+ @logger.error('invalid command parameter.')
181
+ @logger.error($!)
182
+ yield([ "#{tag} BAD invalid command parameter\r\n" ])
183
+ rescue
184
+ raise if ($!.class.name =~ /AssertionFailedError/)
185
+ @logger.error('internal server error.')
186
+ @logger.error($!)
187
+ yield([ "#{tag} BAD internal server error\r\n" ])
188
+ end
189
+ end
190
+ private :guard_error
191
+
192
+ class << self
193
+ def imap_command(name)
194
+ orig_name = "_#{name}".to_sym
195
+ alias_method orig_name, name
196
+ define_method name, lambda{|tag, *args, **name_args, &block|
197
+ guard_error(tag, orig_name, *args, **name_args, &block)
198
+ }
199
+ name.to_sym
200
+ end
201
+ private :imap_command
202
+
203
+ def fetch_mail_store_holder_and_on_demand_recovery(mail_store_pool, username,
204
+ write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
205
+ logger: Logger.new(STDOUT))
206
+ unique_user_id = Authentication.unique_user_id(username)
207
+ logger.debug("unique user ID: #{username} -> #{unique_user_id}") if logger.debug?
208
+
209
+ mail_store_holder = mail_store_pool.get(unique_user_id, timeout_seconds: write_lock_timeout_seconds) {
210
+ logger.info("open mail store: #{unique_user_id} [ #{username} ]")
211
+ }
212
+
213
+ mail_store_holder.write_synchronize(write_lock_timeout_seconds) {
214
+ if (mail_store_holder.mail_store.abort_transaction?) then
215
+ logger.warn("user data recovery start: #{username}")
216
+ yield("* OK [ALERT] start user data recovery.\r\n")
217
+ mail_store_holder.mail_store.recovery_data(logger: logger).sync
218
+ logger.warn("user data recovery end: #{username}")
219
+ yield("* OK completed user data recovery.\r\n")
220
+ end
221
+ }
222
+
223
+ mail_store_holder
224
+ end
225
+ end
226
+
227
+ def ok_greeting
228
+ yield([ "* OK RIMS v#{VERSION} IMAP4rev1 service ready.\r\n" ])
229
+ end
230
+
231
+ def capability(tag)
232
+ capability_list = %w[ IMAP4rev1 UIDPLUS IDLE ]
233
+ capability_list += @auth.capability.map{|auth_capability| "AUTH=#{auth_capability}" }
234
+ res = []
235
+ res << "* CAPABILITY #{capability_list.join(' ')}\r\n"
236
+ res << "#{tag} OK CAPABILITY completed\r\n"
237
+ yield(res)
238
+ end
239
+ imap_command :capability
240
+
241
+ def next_decoder
242
+ self
243
+ end
244
+ end
245
+
246
+ class InitialDecoder < Decoder
247
+ def initialize(mail_store_pool, auth, logger,
248
+ mail_delivery_user: Server::DEFAULT[:mail_delivery_user],
249
+ write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
250
+ **next_decoder_optional)
251
+ super(auth, logger)
252
+ @next_decoder = self
253
+ @mail_store_pool = mail_store_pool
254
+ @folder = nil
255
+ @auth = auth
256
+ @mail_delivery_user = mail_delivery_user
257
+ @write_lock_timeout_seconds = write_lock_timeout_seconds
258
+ @next_decoder_optional = next_decoder_optional
259
+ end
260
+
261
+ attr_reader :next_decoder
262
+
263
+ def auth?
264
+ false
265
+ end
266
+
267
+ def selected?
268
+ false
269
+ end
270
+
271
+ def cleanup
272
+ nil
273
+ end
274
+
275
+ def not_authenticated_response(tag)
276
+ [ "#{tag} NO not authenticated\r\n" ]
277
+ end
278
+ private :not_authenticated_response
279
+
280
+ def noop(tag)
281
+ yield([ "#{tag} OK NOOP completed\r\n" ])
282
+ end
283
+ imap_command :noop
284
+
285
+ def logout(tag)
286
+ cleanup
287
+ res = []
288
+ res << "* BYE server logout\r\n"
289
+ res << "#{tag} OK LOGOUT completed\r\n"
290
+ yield(res)
291
+ end
292
+ imap_command :logout
293
+
294
+ def accept_authentication(username)
295
+ cleanup
296
+
297
+ case (username)
298
+ when @mail_delivery_user
299
+ @logger.info("mail delivery user: #{username}")
300
+ MailDeliveryDecoder.new(@mail_store_pool, @auth, @logger,
301
+ write_lock_timeout_seconds: @write_lock_timeout_seconds,
302
+ **@next_decoder_optional)
303
+ else
304
+ mail_store_holder =
305
+ self.class.fetch_mail_store_holder_and_on_demand_recovery(@mail_store_pool, username,
306
+ write_lock_timeout_seconds: @write_lock_timeout_seconds,
307
+ logger: @logger) {|msg| yield(msg) }
308
+ UserMailboxDecoder.new(self, mail_store_holder, @auth, @logger,
309
+ write_lock_timeout_seconds: @write_lock_timeout_seconds,
310
+ **@next_decoder_optional)
311
+ end
312
+ end
313
+ private :accept_authentication
314
+
315
+ def authenticate(tag, client_response_input_stream, server_challenge_output_stream,
316
+ auth_type, inline_client_response_data_base64=nil)
317
+ auth_reader = AuthenticationReader.new(@auth, client_response_input_stream, server_challenge_output_stream, @logger)
318
+ if (username = auth_reader.authenticate_client(auth_type, inline_client_response_data_base64)) then
319
+ if (username != :*) then
320
+ yield response_stream(tag) {|res|
321
+ @logger.info("authentication OK: #{username}")
322
+ @next_decoder = accept_authentication(username) {|msg| res << msg }
323
+ res << "#{tag} OK AUTHENTICATE #{auth_type} success\r\n"
324
+ }
325
+ else
326
+ @logger.info('bad authentication.')
327
+ yield([ "#{tag} BAD AUTHENTICATE failed\r\n" ])
328
+ end
329
+ else
330
+ yield([ "#{tag} NO authentication failed\r\n" ])
331
+ end
332
+ end
333
+ imap_command :authenticate
334
+
335
+ def login(tag, username, password)
336
+ if (@auth.authenticate_login(username, password)) then
337
+ yield response_stream(tag) {|res|
338
+ @logger.info("login authentication OK: #{username}")
339
+ @next_decoder = accept_authentication(username) {|msg| res << msg }
340
+ res << "#{tag} OK LOGIN completed\r\n"
341
+ }
342
+ else
343
+ yield([ "#{tag} NO failed to login\r\n" ])
344
+ end
345
+ end
346
+ imap_command :login
347
+
348
+ def select(tag, mbox_name)
349
+ yield(not_authenticated_response(tag))
350
+ end
351
+ imap_command :select
352
+
353
+ def examine(tag, mbox_name)
354
+ yield(not_authenticated_response(tag))
355
+ end
356
+ imap_command :examine
357
+
358
+ def create(tag, mbox_name)
359
+ yield(not_authenticated_response(tag))
360
+ end
361
+ imap_command :create
362
+
363
+ def delete(tag, mbox_name)
364
+ yield(not_authenticated_response(tag))
365
+ end
366
+ imap_command :delete
367
+
368
+ def rename(tag, src_name, dst_name)
369
+ yield(not_authenticated_response(tag))
370
+ end
371
+ imap_command :rename
372
+
373
+ def subscribe(tag, mbox_name)
374
+ yield(not_authenticated_response(tag))
375
+ end
376
+ imap_command :subscribe
377
+
378
+ def unsubscribe(tag, mbox_name)
379
+ yield(not_authenticated_response(tag))
380
+ end
381
+ imap_command :unsubscribe
382
+
383
+ def list(tag, ref_name, mbox_name)
384
+ yield(not_authenticated_response(tag))
385
+ end
386
+ imap_command :list
387
+
388
+ def lsub(tag, ref_name, mbox_name)
389
+ yield(not_authenticated_response(tag))
390
+ end
391
+ imap_command :lsub
392
+
393
+ def status(tag, mbox_name, data_item_group)
394
+ yield(not_authenticated_response(tag))
395
+ end
396
+ imap_command :status
397
+
398
+ def append(tag, mbox_name, *opt_args, msg_text)
399
+ yield(not_authenticated_response(tag))
400
+ end
401
+ imap_command :append
402
+
403
+ def check(tag)
404
+ yield(not_authenticated_response(tag))
405
+ end
406
+ imap_command :check
407
+
408
+ def close(tag)
409
+ yield(not_authenticated_response(tag))
410
+ end
411
+ imap_command :close
412
+
413
+ def expunge(tag)
414
+ yield(not_authenticated_response(tag))
415
+ end
416
+ imap_command :expunge
417
+
418
+ def search(tag, *cond_args, uid: false)
419
+ yield(not_authenticated_response(tag))
420
+ end
421
+ imap_command :search
422
+
423
+ def fetch(tag, msg_set, data_item_group, uid: false)
424
+ yield(not_authenticated_response(tag))
425
+ end
426
+ imap_command :fetch
427
+
428
+ def store(tag, msg_set, data_item_name, data_item_value, uid: false)
429
+ yield(not_authenticated_response(tag))
430
+ end
431
+ imap_command :store
432
+
433
+ def copy(tag, msg_set, mbox_name, uid: false)
434
+ yield(not_authenticated_response(tag))
435
+ end
436
+ imap_command :copy
437
+
438
+ def idle(tag, client_input_stream, server_output_stream)
439
+ yield(not_authenticated_response(tag))
440
+ end
441
+ imap_command :idle
442
+ end
443
+
444
+ class AuthenticatedDecoder < Decoder
445
+ def authenticate(tag, client_response_input_stream, server_challenge_output_stream,
446
+ auth_type, inline_client_response_data_base64=nil, &block)
447
+ yield([ "#{tag} NO duplicated authentication\r\n" ])
448
+ end
449
+ imap_command :authenticate
450
+
451
+ def login(tag, username, password, &block)
452
+ yield([ "#{tag} NO duplicated login\r\n" ])
453
+ end
454
+ imap_command :login
455
+ end
456
+
457
+ class UserMailboxDecoder < AuthenticatedDecoder
458
+ def initialize(parent_decoder, mail_store_holder, auth, logger,
459
+ read_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
460
+ write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
461
+ cleanup_write_lock_timeout_seconds: 1)
462
+ super(auth, logger)
463
+ @parent_decoder = parent_decoder
464
+ @mail_store_holder = mail_store_holder
465
+ @read_lock_timeout_seconds = read_lock_timeout_seconds
466
+ @write_lock_timeout_seconds = write_lock_timeout_seconds
467
+ @cleanup_write_lock_timeout_seconds = cleanup_write_lock_timeout_seconds
468
+ @folder = nil
469
+ end
470
+
471
+ def get_mail_store
472
+ @mail_store_holder.mail_store
473
+ end
474
+ private :get_mail_store
475
+
476
+ def auth?
477
+ @mail_store_holder != nil
478
+ end
479
+
480
+ def selected?
481
+ @folder != nil
482
+ end
483
+
484
+ def alive_folder?
485
+ get_mail_store.mbox_name(@folder.mbox_id) != nil
486
+ end
487
+ private :alive_folder?
488
+
489
+ def close_folder(&block)
490
+ if (auth? && selected? && alive_folder?) then
491
+ @folder.reload if @folder.updated?
492
+ @folder.close(&block)
493
+ @folder = nil
494
+ end
495
+
496
+ nil
497
+ end
498
+ private :close_folder
499
+
500
+ def cleanup
501
+ unless (@mail_store_holder.nil?) then
502
+ begin
503
+ @mail_store_holder.write_synchronize(@cleanup_write_lock_timeout_seconds) {
504
+ close_folder
505
+ @mail_store_holder.mail_store.sync
506
+ }
507
+ rescue WriteLockTimeoutError
508
+ @logger.warn("give up to close folder becaue of write-lock timeout over #{@write_lock_timeout_seconds} seconds")
509
+ @folder = nil
510
+ end
511
+ tmp_mail_store_holder = @mail_store_holder
512
+ ReadWriteLock.write_lock_timeout_detach(@cleanup_write_lock_timeout_seconds, @write_lock_timeout_seconds, logger: @logger) {|timeout_seconds|
513
+ tmp_mail_store_holder.return_pool(timeout_seconds: timeout_seconds) {
514
+ @logger.info("close mail store: #{tmp_mail_store_holder.unique_user_id}")
515
+ }
516
+ }
517
+ @mail_store_holder = nil
518
+ end
519
+
520
+ unless (@parent_decoder.nil?) then
521
+ @parent_decoder.cleanup
522
+ @parent_decoder = nil
523
+ end
524
+
525
+ nil
526
+ end
527
+
528
+ def should_be_alive_folder
529
+ alive_folder? or raise "deleted folder: #{@folder.mbox_id}"
530
+ end
531
+ private :should_be_alive_folder
532
+
533
+ def guard_authenticated(tag, imap_command, *args, exclusive: false, **name_args)
534
+ if (auth?) then
535
+ if (exclusive.nil?) then
536
+ guard_error(tag, imap_command, *args, **name_args) {|res|
537
+ yield(res)
538
+ }
539
+ else
540
+ begin
541
+ if (exclusive) then
542
+ @mail_store_holder.write_synchronize(@write_lock_timeout_seconds) {
543
+ guard_authenticated(tag, imap_command, *args, exclusive: nil, **name_args) {|res|
544
+ yield(res)
545
+ }
546
+ }
547
+ else
548
+ @mail_store_holder.read_synchronize(@read_lock_timeout_seconds){
549
+ guard_authenticated(tag, imap_command, *args, exclusive: nil, **name_args) {|res|
550
+ yield(res)
551
+ }
552
+ }
553
+ end
554
+ rescue ReadLockTimeoutError
555
+ @logger.error("write-lock timeout over #{@write_lock_timeout_seconds} seconds")
556
+ yield([ "#{tag} BAD write-lock timeout over #{@write_lock_timeout_seconds} seconds" ])
557
+ rescue WriteLockTimeoutError
558
+ @logger.error("read-lock timeout over #{@read_lock_timeout_seconds} seconds")
559
+ yield([ "#{tag} BAD read-lock timeout over #{@read_lock_timeout_seconds} seconds" ])
560
+ end
561
+ end
562
+ else
563
+ yield([ "#{tag} NO not authenticated\r\n" ])
564
+ end
565
+ end
566
+ private :guard_authenticated
567
+
568
+ def guard_selected(tag, imap_command, *args, **name_args)
569
+ if (selected?) then
570
+ guard_authenticated(tag, imap_command, *args, **name_args) {|res|
571
+ yield(res)
572
+ }
573
+ else
574
+ yield([ "#{tag} NO not selected\r\n" ])
575
+ end
576
+ end
577
+ private :guard_selected
578
+
579
+ class << self
580
+ def imap_command_authenticated(name, **guard_optional)
581
+ orig_name = "_#{name}".to_sym
582
+ alias_method orig_name, name
583
+ define_method name, lambda{|tag, *args, **name_args, &block|
584
+ guard_authenticated(tag, orig_name, *args, **name_args.merge(guard_optional), &block)
585
+ }
586
+ name.to_sym
587
+ end
588
+ private :imap_command_authenticated
589
+
590
+ def imap_command_selected(name, **guard_optional)
591
+ orig_name = "_#{name}".to_sym
592
+ alias_method orig_name, name
593
+ define_method name, lambda{|tag, *args, **name_args, &block|
594
+ guard_selected(tag, orig_name, *args, **name_args.merge(guard_optional), &block)
595
+ }
596
+ name.to_sym
597
+ end
598
+ private :imap_command_selected
599
+ end
600
+
601
+ def noop(tag)
602
+ res = []
603
+ if (auth? && selected?) then
604
+ begin
605
+ @mail_store_holder.read_synchronize(@read_lock_timeout_seconds) {
606
+ @folder.server_response_fetch{|r| res << r } if @folder.server_response?
607
+ }
608
+ rescue ReadLockTimeoutError
609
+ @logger.warn("give up to get folder status because of read-lock timeout over #{@read_lock_timeout_seconds} seconds")
610
+ end
611
+ end
612
+ res << "#{tag} OK NOOP completed\r\n"
613
+ yield(res)
614
+ end
615
+ imap_command :noop
616
+
617
+ def logout(tag)
618
+ cleanup
619
+ res = []
620
+ res << "* BYE server logout\r\n"
621
+ res << "#{tag} OK LOGOUT completed\r\n"
622
+ yield(res)
623
+ end
624
+ imap_command :logout
625
+
626
+ def folder_open_msgs
627
+ all_msgs = get_mail_store.mbox_msg_num(@folder.mbox_id)
628
+ recent_msgs = get_mail_store.mbox_flag_num(@folder.mbox_id, 'recent')
629
+ unseen_msgs = all_msgs - get_mail_store.mbox_flag_num(@folder.mbox_id, 'seen')
630
+ yield("* #{all_msgs} EXISTS\r\n")
631
+ yield("* #{recent_msgs} RECENT\r\n")
632
+ yield("* OK [UNSEEN #{unseen_msgs}]\r\n")
633
+ yield("* OK [UIDVALIDITY #{@folder.mbox_id}]\r\n")
634
+ yield("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n")
635
+ nil
636
+ end
637
+ private :folder_open_msgs
638
+
639
+ def select(tag, mbox_name)
640
+ res = []
641
+ @folder = nil
642
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
643
+ if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
644
+ @folder = get_mail_store.select_mbox(id)
645
+ folder_open_msgs do |msg|
646
+ res << msg
647
+ end
648
+ res << "#{tag} OK [READ-WRITE] SELECT completed\r\n"
649
+ else
650
+ res << "#{tag} NO not found a mailbox\r\n"
651
+ end
652
+ yield(res)
653
+ end
654
+ imap_command_authenticated :select
655
+
656
+ def examine(tag, mbox_name)
657
+ res = []
658
+ @folder = nil
659
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
660
+ if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
661
+ @folder = get_mail_store.examine_mbox(id)
662
+ folder_open_msgs do |msg|
663
+ res << msg
664
+ end
665
+ res << "#{tag} OK [READ-ONLY] EXAMINE completed\r\n"
666
+ else
667
+ res << "#{tag} NO not found a mailbox\r\n"
668
+ end
669
+ yield(res)
670
+ end
671
+ imap_command_authenticated :examine
672
+
673
+ def create(tag, mbox_name)
674
+ res = []
675
+ @folder.server_response_fetch{|r| res << r } if selected?
676
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
677
+ if (get_mail_store.mbox_id(mbox_name_utf8)) then
678
+ res << "#{tag} NO duplicated mailbox\r\n"
679
+ else
680
+ get_mail_store.add_mbox(mbox_name_utf8)
681
+ res << "#{tag} OK CREATE completed\r\n"
682
+ end
683
+ yield(res)
684
+ end
685
+ imap_command_authenticated :create, exclusive: true
686
+
687
+ def delete(tag, mbox_name)
688
+ res = []
689
+ @folder.server_response_fetch{|r| res << r } if selected?
690
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
691
+ if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
692
+ if (id != get_mail_store.mbox_id('INBOX')) then
693
+ get_mail_store.del_mbox(id)
694
+ res << "#{tag} OK DELETE completed\r\n"
695
+ else
696
+ res << "#{tag} NO not delete inbox\r\n"
697
+ end
698
+ else
699
+ res << "#{tag} NO not found a mailbox\r\n"
700
+ end
701
+ yield(res)
702
+ end
703
+ imap_command_authenticated :delete, exclusive: true
704
+
705
+ def rename(tag, src_name, dst_name)
706
+ res = []
707
+ @folder.server_response_fetch{|r| res << r } if selected?
708
+ src_name_utf8 = Net::IMAP.decode_utf7(src_name)
709
+ dst_name_utf8 = Net::IMAP.decode_utf7(dst_name)
710
+ unless (id = get_mail_store.mbox_id(src_name_utf8)) then
711
+ return yield(res << "#{tag} NO not found a mailbox\r\n")
712
+ end
713
+ if (id == get_mail_store.mbox_id('INBOX')) then
714
+ return yield(res << "#{tag} NO not rename inbox\r\n")
715
+ end
716
+ if (get_mail_store.mbox_id(dst_name_utf8)) then
717
+ return yield(res << "#{tag} NO duplicated mailbox\r\n")
718
+ end
719
+ get_mail_store.rename_mbox(id, dst_name_utf8)
720
+ return yield(res << "#{tag} OK RENAME completed\r\n")
721
+ end
722
+ imap_command_authenticated :rename, exclusive: true
723
+
724
+ def subscribe(tag, mbox_name)
725
+ res = []
726
+ @folder.server_response_fetch{|r| res << r } if selected?
727
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
728
+ if (_mbox_id = get_mail_store.mbox_id(mbox_name_utf8)) then
729
+ res << "#{tag} OK SUBSCRIBE completed\r\n"
730
+ else
731
+ res << "#{tag} NO not found a mailbox\r\n"
732
+ end
733
+ yield(res)
734
+ end
735
+ imap_command_authenticated :subscribe
736
+
737
+ def unsubscribe(tag, mbox_name)
738
+ res = []
739
+ @folder.server_response_fetch{|r| res << r } if selected?
740
+ if (_mbox_id = get_mail_store.mbox_id(mbox_name)) then
741
+ res << "#{tag} NO not implemented subscribe/unsbscribe command\r\n"
742
+ else
743
+ res << "#{tag} NO not found a mailbox\r\n"
744
+ end
745
+ yield(res)
746
+ end
747
+ imap_command_authenticated :unsubscribe
748
+
749
+ def list_mbox(ref_name, mbox_name)
750
+ ref_name_utf8 = Net::IMAP.decode_utf7(ref_name)
751
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
752
+
753
+ mbox_filter = Protocol.compile_wildcard(mbox_name_utf8)
754
+ mbox_list = get_mail_store.each_mbox_id.map{|id| [ id, get_mail_store.mbox_name(id) ] }
755
+ mbox_list.keep_if{|id, name| name.start_with? ref_name_utf8 }
756
+ mbox_list.keep_if{|id, name| name[(ref_name_utf8.length)..-1] =~ mbox_filter }
757
+
758
+ for id, name_utf8 in mbox_list
759
+ name = Net::IMAP.encode_utf7(name_utf8)
760
+ attrs = '\Noinferiors'
761
+ if (get_mail_store.mbox_flag_num(id, 'recent') > 0) then
762
+ attrs << ' \Marked'
763
+ else
764
+ attrs << ' \Unmarked'
765
+ end
766
+ yield("(#{attrs}) NIL #{Protocol.quote(name)}")
767
+ end
768
+
769
+ nil
770
+ end
771
+ private :list_mbox
772
+
773
+ def list(tag, ref_name, mbox_name)
774
+ res = []
775
+ @folder.server_response_fetch{|r| res << r } if selected?
776
+ if (mbox_name.empty?) then
777
+ res << "* LIST (\\Noselect) NIL \"\"\r\n"
778
+ else
779
+ list_mbox(ref_name, mbox_name) do |mbox_entry|
780
+ res << "* LIST #{mbox_entry}\r\n"
781
+ end
782
+ end
783
+ res << "#{tag} OK LIST completed\r\n"
784
+ yield(res)
785
+ end
786
+ imap_command_authenticated :list
787
+
788
+ def lsub(tag, ref_name, mbox_name)
789
+ res = []
790
+ @folder.server_response_fetch{|r| res << r } if selected?
791
+ if (mbox_name.empty?) then
792
+ res << "* LSUB (\\Noselect) NIL \"\"\r\n"
793
+ else
794
+ list_mbox(ref_name, mbox_name) do |mbox_entry|
795
+ res << "* LSUB #{mbox_entry}\r\n"
796
+ end
797
+ end
798
+ res << "#{tag} OK LSUB completed\r\n"
799
+ yield(res)
800
+ end
801
+ imap_command_authenticated :lsub
802
+
803
+ def status(tag, mbox_name, data_item_group)
804
+ res = []
805
+ @folder.server_response_fetch{|r| res << r } if selected?
806
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
807
+ if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
808
+ unless ((data_item_group.is_a? Array) && (data_item_group[0] == :group)) then
809
+ raise SyntaxError, 'second arugment is not a group list.'
810
+ end
811
+
812
+ values = []
813
+ for item in data_item_group[1..-1]
814
+ case (item.upcase)
815
+ when 'MESSAGES'
816
+ values << 'MESSAGES' << get_mail_store.mbox_msg_num(id)
817
+ when 'RECENT'
818
+ values << 'RECENT' << get_mail_store.mbox_flag_num(id, 'recent')
819
+ when 'UIDNEXT'
820
+ values << 'UIDNEXT' << get_mail_store.uid(id)
821
+ when 'UIDVALIDITY'
822
+ values << 'UIDVALIDITY' << id
823
+ when 'UNSEEN'
824
+ unseen_flags = get_mail_store.mbox_msg_num(id) - get_mail_store.mbox_flag_num(id, 'seen')
825
+ values << 'UNSEEN' << unseen_flags
826
+ else
827
+ raise SyntaxError, "unknown status data: #{item}"
828
+ end
829
+ end
830
+
831
+ res << "* STATUS #{Protocol.quote(mbox_name)} (#{values.join(' ')})\r\n"
832
+ res << "#{tag} OK STATUS completed\r\n"
833
+ else
834
+ res << "#{tag} NO not found a mailbox\r\n"
835
+ end
836
+ yield(res)
837
+ end
838
+ imap_command_authenticated :status
839
+
840
+ def mailbox_size_server_response_multicast_push(mbox_id)
841
+ all_msgs = get_mail_store.mbox_msg_num(mbox_id)
842
+ recent_msgs = get_mail_store.mbox_flag_num(mbox_id, 'recent')
843
+
844
+ f = get_mail_store.examine_mbox(mbox_id)
845
+ begin
846
+ f.server_response_multicast_push("* #{all_msgs} EXISTS\r\n")
847
+ f.server_response_multicast_push("* #{recent_msgs} RECENT\r\n")
848
+ ensure
849
+ f.close
850
+ end
851
+
852
+ nil
853
+ end
854
+ private :mailbox_size_server_response_multicast_push
855
+
856
+ def append(tag, mbox_name, *opt_args, msg_text)
857
+ res = []
858
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
859
+ if (mbox_id = get_mail_store.mbox_id(mbox_name_utf8)) then
860
+ msg_flags = []
861
+ msg_date = Time.now
862
+
863
+ if ((! opt_args.empty?) && (opt_args[0].is_a? Array)) then
864
+ opt_flags = opt_args.shift
865
+ if (opt_flags[0] != :group) then
866
+ raise SyntaxError, 'bad flag list.'
867
+ end
868
+ for flag_atom in opt_flags[1..-1]
869
+ case (flag_atom.upcase)
870
+ when '\ANSWERED'
871
+ msg_flags << 'answered'
872
+ when '\FLAGGED'
873
+ msg_flags << 'flagged'
874
+ when '\DELETED'
875
+ msg_flags << 'deleted'
876
+ when '\SEEN'
877
+ msg_flags << 'seen'
878
+ when '\DRAFT'
879
+ msg_flags << 'draft'
880
+ else
881
+ raise SyntaxError, "invalid flag: #{flag_atom}"
882
+ end
883
+ end
884
+ end
885
+
886
+ if ((! opt_args.empty?) && (opt_args[0].is_a? String)) then
887
+ begin
888
+ msg_date = Time.parse(opt_args.shift)
889
+ rescue ArgumentError
890
+ raise SyntaxError, $!.message
891
+ end
892
+ end
893
+
894
+ unless (opt_args.empty?) then
895
+ raise SyntaxError, "unknown option: #{opt_args.inspect}"
896
+ end
897
+
898
+ uid = get_mail_store.add_msg(mbox_id, msg_text, msg_date)
899
+ for flag_name in msg_flags
900
+ get_mail_store.set_msg_flag(mbox_id, uid, flag_name, true)
901
+ end
902
+ mailbox_size_server_response_multicast_push(mbox_id)
903
+
904
+ @folder.server_response_fetch{|r| res << r } if selected?
905
+ res << "#{tag} OK [APPENDUID #{mbox_id} #{uid}] APPEND completed\r\n"
906
+ else
907
+ @folder.server_response_fetch{|r| res << r } if selected?
908
+ res << "#{tag} NO [TRYCREATE] not found a mailbox\r\n"
909
+ end
910
+ yield(res)
911
+ end
912
+ imap_command_authenticated :append, exclusive: true
913
+
914
+ def check(tag)
915
+ res = []
916
+ @folder.server_response_fetch{|r| res << r }
917
+ get_mail_store.sync
918
+ res << "#{tag} OK CHECK completed\r\n"
919
+ yield(res)
920
+ end
921
+ imap_command_selected :check, exclusive: true
922
+
923
+ def close(tag, &block)
924
+ yield response_stream(tag) {|res|
925
+ @folder.server_response_fetch{|r| res << r }
926
+ close_folder do |msg_num|
927
+ r = "* #{msg_num} EXPUNGE\r\n"
928
+ res << r
929
+ @folder.server_response_multicast_push(r)
930
+ end
931
+ get_mail_store.sync
932
+ res << "#{tag} OK CLOSE completed\r\n"
933
+ }
934
+ end
935
+ imap_command_selected :close, exclusive: true
936
+
937
+ def expunge(tag)
938
+ return yield([ "#{tag} NO cannot expunge in read-only mode\r\n" ]) if @folder.read_only?
939
+ should_be_alive_folder
940
+ @folder.reload if @folder.updated?
941
+
942
+ yield response_stream(tag) {|res|
943
+ @folder.server_response_fetch{|r| res << r }
944
+ @folder.expunge_mbox do |msg_num|
945
+ r = "* #{msg_num} EXPUNGE\r\n"
946
+ res << r
947
+ @folder.server_response_multicast_push(r)
948
+ end
949
+ res << "#{tag} OK EXPUNGE completed\r\n"
950
+ }
951
+ end
952
+ imap_command_selected :expunge, exclusive: true
953
+
954
+ def search(tag, *cond_args, uid: false)
955
+ should_be_alive_folder
956
+ @folder.reload if @folder.updated?
957
+ parser = Protocol::SearchParser.new(get_mail_store, @folder)
958
+
959
+ if (! cond_args.empty? && cond_args[0].upcase == 'CHARSET') then
960
+ cond_args.shift
961
+ charset_string = cond_args.shift or raise SyntaxError, 'need for a charset string of CHARSET'
962
+ charset_string.is_a? String or raise SyntaxError, "CHARSET charset string expected as <String> but was <#{charset_string.class}>."
963
+ parser.charset = charset_string
964
+ end
965
+
966
+ if (cond_args.empty?) then
967
+ raise SyntaxError, 'required search arguments.'
968
+ end
969
+
970
+ if (cond_args[0].upcase == 'UID' && cond_args.length >= 2) then
971
+ begin
972
+ msg_set = @folder.parse_msg_set(cond_args[1], uid: true)
973
+ msg_src = @folder.msg_find_all(msg_set, uid: true)
974
+ cond_args.shift(2)
975
+ rescue MessageSetSyntaxError
976
+ msg_src = @folder.each_msg
977
+ end
978
+ else
979
+ begin
980
+ msg_set = @folder.parse_msg_set(cond_args[0], uid: false)
981
+ msg_src = @folder.msg_find_all(msg_set, uid: false)
982
+ cond_args.shift
983
+ rescue MessageSetSyntaxError
984
+ msg_src = @folder.each_msg
985
+ end
986
+ end
987
+ cond = parser.parse(cond_args)
988
+
989
+ yield response_stream(tag) {|res|
990
+ @folder.server_response_fetch{|r| res << r }
991
+ res << '* SEARCH'
992
+ for msg in msg_src
993
+ if (cond.call(msg)) then
994
+ if (uid) then
995
+ res << " #{msg.uid}"
996
+ else
997
+ res << " #{msg.num}"
998
+ end
999
+ end
1000
+ end
1001
+ res << "\r\n"
1002
+ res << "#{tag} OK SEARCH completed\r\n"
1003
+ }
1004
+ end
1005
+ imap_command_selected :search
1006
+
1007
+ def fetch(tag, msg_set, data_item_group, uid: false)
1008
+ should_be_alive_folder
1009
+ @folder.reload if @folder.updated?
1010
+
1011
+ msg_set = @folder.parse_msg_set(msg_set, uid: uid)
1012
+ msg_list = @folder.msg_find_all(msg_set, uid: uid)
1013
+
1014
+ unless ((data_item_group.is_a? Array) && data_item_group[0] == :group) then
1015
+ data_item_group = [ :group, data_item_group ]
1016
+ end
1017
+ if (uid) then
1018
+ unless (data_item_group.find{|i| (i.is_a? String) && (i.upcase == 'UID') }) then
1019
+ data_item_group = [ :group, 'UID' ] + data_item_group[1..-1]
1020
+ end
1021
+ end
1022
+
1023
+ parser = Protocol::FetchParser.new(get_mail_store, @folder)
1024
+ fetch = parser.parse(data_item_group)
1025
+
1026
+ yield response_stream(tag) {|res|
1027
+ @folder.server_response_fetch{|r| res << r }
1028
+ for msg in msg_list
1029
+ res << ('* '.b << msg.num.to_s.b << ' FETCH '.b << fetch.call(msg) << "\r\n".b)
1030
+ end
1031
+ res << "#{tag} OK FETCH completed\r\n"
1032
+ }
1033
+ end
1034
+ imap_command_selected :fetch
1035
+
1036
+ def store(tag, msg_set, data_item_name, data_item_value, uid: false)
1037
+ return yield([ "#{tag} NO cannot store in read-only mode\r\n" ]) if @folder.read_only?
1038
+ should_be_alive_folder
1039
+ @folder.reload if @folder.updated?
1040
+
1041
+ msg_set = @folder.parse_msg_set(msg_set, uid: uid)
1042
+ name, option = data_item_name.split(/\./, 2)
1043
+
1044
+ case (name.upcase)
1045
+ when 'FLAGS'
1046
+ action = :flags_replace
1047
+ when '+FLAGS'
1048
+ action = :flags_add
1049
+ when '-FLAGS'
1050
+ action = :flags_del
1051
+ else
1052
+ raise SyntaxError, "unknown store action: #{name}"
1053
+ end
1054
+
1055
+ case (option && option.upcase)
1056
+ when 'SILENT'
1057
+ is_silent = true
1058
+ when nil
1059
+ is_silent = false
1060
+ else
1061
+ raise SyntaxError, "unknown store option: #{option.inspect}"
1062
+ end
1063
+
1064
+ if ((data_item_value.is_a? Array) && data_item_value[0] == :group) then
1065
+ flag_list = []
1066
+ for flag_atom in data_item_value[1..-1]
1067
+ case (flag_atom.upcase)
1068
+ when '\ANSWERED'
1069
+ flag_list << 'answered'
1070
+ when '\FLAGGED'
1071
+ flag_list << 'flagged'
1072
+ when '\DELETED'
1073
+ flag_list << 'deleted'
1074
+ when '\SEEN'
1075
+ flag_list << 'seen'
1076
+ when '\DRAFT'
1077
+ flag_list << 'draft'
1078
+ else
1079
+ raise SyntaxError, "invalid flag: #{flag_atom}"
1080
+ end
1081
+ end
1082
+ rest_flag_list = (MailStore::MSG_FLAG_NAMES - %w[ recent ]) - flag_list
1083
+ else
1084
+ raise SyntaxError, 'third arugment is not a group list.'
1085
+ end
1086
+
1087
+ msg_list = @folder.msg_find_all(msg_set, uid: uid)
1088
+
1089
+ for msg in msg_list
1090
+ case (action)
1091
+ when :flags_replace
1092
+ for name in flag_list
1093
+ get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, true)
1094
+ end
1095
+ for name in rest_flag_list
1096
+ get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, false)
1097
+ end
1098
+ when :flags_add
1099
+ for name in flag_list
1100
+ get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, true)
1101
+ end
1102
+ when :flags_del
1103
+ for name in flag_list
1104
+ get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, false)
1105
+ end
1106
+ else
1107
+ raise "internal error: unknown action: #{action}"
1108
+ end
1109
+ end
1110
+
1111
+ if (is_silent) then
1112
+ silent_res = []
1113
+ @folder.server_response_fetch{|r| silent_res << r }
1114
+ silent_res << "#{tag} OK STORE completed\r\n"
1115
+ yield(silent_res)
1116
+ else
1117
+ yield response_stream(tag) {|res|
1118
+ @folder.server_response_fetch{|r| res << r }
1119
+ for msg in msg_list
1120
+ flag_atom_list = nil
1121
+
1122
+ if (get_mail_store.msg_exist? @folder.mbox_id, msg.uid) then
1123
+ flag_atom_list = []
1124
+ for name in MailStore::MSG_FLAG_NAMES
1125
+ if (get_mail_store.msg_flag(@folder.mbox_id, msg.uid, name)) then
1126
+ flag_atom_list << "\\#{name.capitalize}"
1127
+ end
1128
+ end
1129
+ end
1130
+
1131
+ if (flag_atom_list) then
1132
+ if (uid) then
1133
+ res << "* #{msg.num} FETCH (UID #{msg.uid} FLAGS (#{flag_atom_list.join(' ')}))\r\n"
1134
+ else
1135
+ res << "* #{msg.num} FETCH (FLAGS (#{flag_atom_list.join(' ')}))\r\n"
1136
+ end
1137
+ else
1138
+ @logger.warn("not found a message and skipped: uidvalidity(#{@folder.mbox_id}) uid(#{msg.uid})")
1139
+ end
1140
+ end
1141
+ res << "#{tag} OK STORE completed\r\n"
1142
+ }
1143
+ end
1144
+ end
1145
+ imap_command_selected :store, exclusive: true
1146
+
1147
+ def copy(tag, msg_set, mbox_name, uid: false)
1148
+ should_be_alive_folder
1149
+ @folder.reload if @folder.updated?
1150
+
1151
+ res = []
1152
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
1153
+ msg_set = @folder.parse_msg_set(msg_set, uid: uid)
1154
+
1155
+ if (mbox_id = get_mail_store.mbox_id(mbox_name_utf8)) then
1156
+ msg_list = @folder.msg_find_all(msg_set, uid: uid)
1157
+
1158
+ src_uids = []
1159
+ dst_uids = []
1160
+ for msg in msg_list
1161
+ src_uids << msg.uid
1162
+ dst_uids << get_mail_store.copy_msg(msg.uid, @folder.mbox_id, mbox_id)
1163
+ end
1164
+
1165
+ if msg_list.size > 0
1166
+ mailbox_size_server_response_multicast_push(mbox_id)
1167
+ @folder.server_response_fetch{|r| res << r }
1168
+ res << "#{tag} OK [COPYUID #{mbox_id} #{src_uids.join(',')} #{dst_uids.join(',')}] COPY completed\r\n"
1169
+ else
1170
+ @folder.server_response_fetch{|r| res << r }
1171
+ res << "#{tag} OK COPY completed\r\n"
1172
+ end
1173
+ else
1174
+ @folder.server_response_fetch{|r| res << r }
1175
+ res << "#{tag} NO [TRYCREATE] not found a mailbox\r\n"
1176
+ end
1177
+ yield(res)
1178
+ end
1179
+ imap_command_selected :copy, exclusive: true
1180
+
1181
+ def idle(tag, client_input_stream, server_output_stream)
1182
+ @logger.info('idle start...')
1183
+ server_output_stream.write("+ continue\r\n")
1184
+ server_output_stream.flush
1185
+
1186
+ server_response_thread = Thread.new{
1187
+ @logger.info('idle server response thread start... ')
1188
+ @folder.server_response_idle_wait{|server_response_list|
1189
+ @logger.debug("idle server response: #{server_response}") if @logger.debug?
1190
+ for server_response in server_response_list
1191
+ server_output_stream.write(server_response)
1192
+ end
1193
+ server_output_stream.flush
1194
+ }
1195
+ server_output_stream.flush
1196
+ @logger.info('idle server response thread terminated.')
1197
+ }
1198
+
1199
+ begin
1200
+ line = client_input_stream.gets
1201
+ ensure
1202
+ @folder.server_response_idle_interrupt
1203
+ server_response_thread.join
1204
+ end
1205
+
1206
+ res = []
1207
+ if (line) then
1208
+ line.chomp!("\n")
1209
+ line.chomp!("\r")
1210
+ if (line.upcase == "DONE") then
1211
+ @logger.info('idle terminated.')
1212
+ res << "#{tag} OK IDLE terminated\r\n"
1213
+ else
1214
+ @logger.warn('unexpected client response and idle terminated.')
1215
+ @logger.debug("unexpected client response data: #{line}")
1216
+ res << "#{tag} BAD unexpected client response\r\n"
1217
+ end
1218
+ else
1219
+ @logger.warn('unexpected client connection close and idle terminated.')
1220
+ res << "#{tag} BAD unexpected client connection close\r\n"
1221
+ end
1222
+ yield(res)
1223
+ end
1224
+ imap_command_selected :idle, exclusive: nil
1225
+ end
1226
+
1227
+ def Decoder.encode_delivery_target_mailbox(username, mbox_name)
1228
+ "b64user-mbox #{Protocol.encode_base64(username)} #{mbox_name}"
1229
+ end
1230
+
1231
+ def Decoder.decode_delivery_target_mailbox(encoded_mbox_name)
1232
+ encode_type, base64_username, mbox_name = encoded_mbox_name.split(' ', 3)
1233
+ if (encode_type != 'b64user-mbox') then
1234
+ raise SyntaxError, "unknown mailbox encode type: #{encode_type}"
1235
+ end
1236
+ return Protocol.decode_base64(base64_username), mbox_name
1237
+ end
1238
+
1239
+ class MailDeliveryDecoder < AuthenticatedDecoder
1240
+ def initialize(mail_store_pool, auth, logger,
1241
+ write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
1242
+ cleanup_write_lock_timeout_seconds: 1,
1243
+ **mailbox_decoder_optional)
1244
+ super(auth, logger)
1245
+ @mail_store_pool = mail_store_pool
1246
+ @auth = auth
1247
+ @write_lock_timeout_seconds = write_lock_timeout_seconds
1248
+ @cleanup_write_lock_timeout_seconds = cleanup_write_lock_timeout_seconds
1249
+ @mailbox_decoder_optional = mailbox_decoder_optional
1250
+ @last_user_cache_key_username = nil
1251
+ @last_user_cache_value_mail_store_holder = nil
1252
+ end
1253
+
1254
+ def user_mail_store_cached?(username)
1255
+ @last_user_cache_key_username == username
1256
+ end
1257
+ private :user_mail_store_cached?
1258
+
1259
+ def fetch_user_mail_store_holder(username)
1260
+ unless (user_mail_store_cached? username) then
1261
+ release_user_mail_store_holder
1262
+ @last_user_cache_value_mail_store_holder = yield
1263
+ @last_user_cache_key_username = username
1264
+ end
1265
+ @last_user_cache_value_mail_store_holder
1266
+ end
1267
+ private :fetch_user_mail_store_holder
1268
+
1269
+ def release_user_mail_store_holder
1270
+ if (@last_user_cache_value_mail_store_holder) then
1271
+ mail_store_holder = @last_user_cache_value_mail_store_holder
1272
+ @last_user_cache_key_username = nil
1273
+ @last_user_cache_value_mail_store_holder = nil
1274
+ ReadWriteLock.write_lock_timeout_detach(@cleanup_write_lock_timeout_seconds, @write_lock_timeout_seconds, logger: @logger) {|timeout_seconds|
1275
+ mail_store_holder.return_pool(timeout_seconds: timeout_seconds) {
1276
+ @logger.info("close cached mail store to deliver message: #{mail_store_holder.unique_user_id}")
1277
+ }
1278
+ }
1279
+ end
1280
+ end
1281
+ private :release_user_mail_store_holder
1282
+
1283
+ def auth?
1284
+ @mail_store_pool != nil
1285
+ end
1286
+
1287
+ def selected?
1288
+ false
1289
+ end
1290
+
1291
+ def cleanup
1292
+ release_user_mail_store_holder
1293
+ @mail_store_pool = nil unless @mail_store_pool.nil?
1294
+ @auth = nil unless @auth.nil?
1295
+ nil
1296
+ end
1297
+
1298
+ def logout(tag)
1299
+ cleanup
1300
+ res = []
1301
+ res << "* BYE server logout\r\n"
1302
+ res << "#{tag} OK LOGOUT completed\r\n"
1303
+ yield(res)
1304
+ end
1305
+ imap_command :logout
1306
+
1307
+ alias standard_capability _capability
1308
+ private :standard_capability
1309
+
1310
+ def capability(tag)
1311
+ standard_capability(tag) {|res|
1312
+ yield res.map{|line|
1313
+ if (line.start_with? '* CAPABILITY ') then
1314
+ line.strip + " X-RIMS-MAIL-DELIVERY-USER\r\n"
1315
+ else
1316
+ line
1317
+ end
1318
+ }
1319
+ }
1320
+ end
1321
+ imap_command :capability
1322
+
1323
+ def not_allowed_command_response(tag)
1324
+ [ "#{tag} NO not allowed command on mail delivery user\r\n" ]
1325
+ end
1326
+ private :not_allowed_command_response
1327
+
1328
+ def select(tag, mbox_name)
1329
+ yield(not_allowed_command_response(tag))
1330
+ end
1331
+ imap_command :select
1332
+
1333
+ def examine(tag, mbox_name)
1334
+ yield(not_allowed_command_response(tag))
1335
+ end
1336
+ imap_command :examine
1337
+
1338
+ def create(tag, mbox_name)
1339
+ yield(not_allowed_command_response(tag))
1340
+ end
1341
+ imap_command :create
1342
+
1343
+ def delete(tag, mbox_name)
1344
+ yield(not_allowed_command_response(tag))
1345
+ end
1346
+ imap_command :delete
1347
+
1348
+ def rename(tag, src_name, dst_name)
1349
+ yield(not_allowed_command_response(tag))
1350
+ end
1351
+ imap_command :rename
1352
+
1353
+ def subscribe(tag, mbox_name)
1354
+ yield(not_allowed_command_response(tag))
1355
+ end
1356
+ imap_command :subscribe
1357
+
1358
+ def unsubscribe(tag, mbox_name)
1359
+ yield(not_allowed_command_response(tag))
1360
+ end
1361
+ imap_command :unsubscribe
1362
+
1363
+ def list(tag, ref_name, mbox_name)
1364
+ yield(not_allowed_command_response(tag))
1365
+ end
1366
+ imap_command :list
1367
+
1368
+ def lsub(tag, ref_name, mbox_name)
1369
+ yield(not_allowed_command_response(tag))
1370
+ end
1371
+ imap_command :lsub
1372
+
1373
+ def status(tag, mbox_name, data_item_group)
1374
+ yield(not_allowed_command_response(tag))
1375
+ end
1376
+ imap_command :status
1377
+
1378
+ def deliver_to_user(tag, username, mbox_name, opt_args, msg_text, mail_store_holder, res)
1379
+ user_decoder = UserMailboxDecoder.new(self, mail_store_holder, @auth, @logger,
1380
+ write_lock_timeout_seconds: @write_lock_timeout_seconds,
1381
+ cleanup_write_lock_timeout_seconds: @cleanup_write_lock_timeout_seconds,
1382
+ **@mailbox_decoder_optional)
1383
+ user_decoder.append(tag, mbox_name, *opt_args, msg_text) {|append_response|
1384
+ if (append_response.last.split(' ', 3)[1] == 'OK') then
1385
+ @logger.info("message delivery: successed to deliver #{msg_text.bytesize} octets message.")
1386
+ else
1387
+ @logger.info("message delivery: failed to deliver message.")
1388
+ end
1389
+ for response_data in append_response
1390
+ res << response_data
1391
+ end
1392
+ }
1393
+ end
1394
+ private :deliver_to_user
1395
+
1396
+ def append(tag, encoded_mbox_name, *opt_args, msg_text)
1397
+ username, mbox_name = self.class.decode_delivery_target_mailbox(encoded_mbox_name)
1398
+ @logger.info("message delivery: user #{username}, mailbox #{mbox_name}")
1399
+
1400
+ if (@auth.user? username) then
1401
+ if (user_mail_store_cached? username) then
1402
+ res = []
1403
+ mail_store_holder = fetch_user_mail_store_holder(username)
1404
+ deliver_to_user(tag, username, mbox_name, opt_args, msg_text, mail_store_holder, res)
1405
+ else
1406
+ res = Enumerator.new{|stream_res|
1407
+ mail_store_holder = fetch_user_mail_store_holder(username) {
1408
+ self.class.fetch_mail_store_holder_and_on_demand_recovery(@mail_store_pool, username,
1409
+ write_lock_timeout_seconds: @write_lock_timeout_seconds,
1410
+ logger: @logger) {|msg| stream_res << msg }
1411
+ }
1412
+ deliver_to_user(tag, username, mbox_name, opt_args, msg_text, mail_store_holder, stream_res)
1413
+ }
1414
+ end
1415
+ yield(res)
1416
+ else
1417
+ @logger.info('message delivery: not found a user.')
1418
+ yield([ "#{tag} NO not found a user and couldn't deliver a message to the user's mailbox\r\n" ])
1419
+ end
1420
+ end
1421
+ imap_command :append
1422
+
1423
+ def check(tag)
1424
+ yield(not_allowed_command_response(tag))
1425
+ end
1426
+ imap_command :check
1427
+
1428
+ def close(tag)
1429
+ yield(not_allowed_command_response(tag))
1430
+ end
1431
+ imap_command :close
1432
+
1433
+ def expunge(tag)
1434
+ yield(not_allowed_command_response(tag))
1435
+ end
1436
+ imap_command :expunge
1437
+
1438
+ def search(tag, *cond_args, uid: false)
1439
+ yield(not_allowed_command_response(tag))
1440
+ end
1441
+ imap_command :search
1442
+
1443
+ def fetch(tag, msg_set, data_item_group, uid: false)
1444
+ yield(not_allowed_command_response(tag))
1445
+ end
1446
+ imap_command :fetch
1447
+
1448
+ def store(tag, msg_set, data_item_name, data_item_value, uid: false)
1449
+ yield(not_allowed_command_response(tag))
1450
+ end
1451
+ imap_command :store
1452
+
1453
+ def copy(tag, msg_set, mbox_name, uid: false)
1454
+ yield(not_allowed_command_response(tag))
1455
+ end
1456
+ imap_command :copy
1457
+
1458
+ def idle(tag, client_input_stream, server_output_stream)
1459
+ yield(not_allowed_command_response(tag))
1460
+ end
1461
+ imap_command :idle
1462
+ end
1463
+ end
1464
+ end
1465
+
1466
+ # Local Variables:
1467
+ # mode: Ruby
1468
+ # indent-tabs-mode: nil
1469
+ # End: