rims 0.2.1

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