imap.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. require 'net/imap'
  3. class Channel::Driver::Imap < Channel::EmailParser
  4. def fetchable?(_channel)
  5. true
  6. end
  7. =begin
  8. fetch emails from IMAP account
  9. instance = Channel::Driver::Imap.new
  10. result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for)
  11. returns
  12. {
  13. result: 'ok',
  14. fetched: 123,
  15. notice: 'e. g. message about to big emails in mailbox',
  16. }
  17. check if connect to IMAP account is possible, return count of mails in mailbox
  18. instance = Channel::Driver::Imap.new
  19. result = instance.fetch(params[:inbound][:options], channel, 'check')
  20. returns
  21. {
  22. result: 'ok',
  23. content_messages: 123,
  24. }
  25. verify IMAP account, check if search email is in there
  26. instance = Channel::Driver::Imap.new
  27. result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for)
  28. returns
  29. {
  30. result: 'ok', # 'verify not ok'
  31. }
  32. example
  33. params = {
  34. host: 'outlook.office365.com',
  35. user: 'xxx@znuny.onmicrosoft.com',
  36. password: 'xxx',
  37. keep_on_server: true,
  38. }
  39. channel = Channel.last
  40. instance = Channel::Driver::Imap.new
  41. result = instance.fetch(params, channel, 'verify')
  42. =end
  43. def fetch(options, channel, check_type = '', verify_string = '')
  44. ssl = true
  45. starttls = false
  46. port = 993
  47. keep_on_server = false
  48. folder = 'INBOX'
  49. if options[:keep_on_server] == true || options[:keep_on_server] == 'true'
  50. keep_on_server = true
  51. end
  52. if options.key?(:ssl) && options[:ssl] == false
  53. ssl = false
  54. port = 143
  55. end
  56. port = if options.key?(:port) && options[:port].present?
  57. options[:port].to_i
  58. elsif ssl == true
  59. 993
  60. else
  61. 143
  62. end
  63. if ssl == true && port != 993
  64. ssl = false
  65. starttls = true
  66. end
  67. if options[:folder].present?
  68. folder = options[:folder]
  69. end
  70. Rails.logger.info "fetching imap (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl},starttls=#{starttls},folder=#{folder},keep_on_server=#{keep_on_server})"
  71. # on check, reduce open_timeout to have faster probing
  72. check_type_timeout = 45
  73. if check_type == 'check'
  74. check_type_timeout = 6
  75. end
  76. timeout(check_type_timeout) do
  77. @imap = ::Net::IMAP.new(options[:host], port, ssl, nil, false)
  78. if starttls
  79. @imap.starttls()
  80. end
  81. end
  82. timeout(check_type_timeout) do
  83. @imap.login(options[:user], options[:password])
  84. end
  85. timeout(check_type_timeout) do
  86. # select folder
  87. @imap.select(folder)
  88. end
  89. # sort messages by date on server (if not supported), if not fetch messages via search (first in, first out)
  90. filter = ['ALL']
  91. if keep_on_server && check_type != 'check' && check_type != 'verify'
  92. filter = %w[NOT SEEN]
  93. end
  94. message_ids = nil
  95. timeout(6.minutes) do
  96. begin
  97. message_ids = @imap.sort(['DATE'], filter, 'US-ASCII')
  98. rescue
  99. message_ids = @imap.search(filter)
  100. end
  101. end
  102. # check mode only
  103. if check_type == 'check'
  104. Rails.logger.info 'check only mode, fetch no emails'
  105. content_max_check = 2
  106. content_messages = 0
  107. # check messages
  108. message_ids.each do |message_id|
  109. message_meta = nil
  110. timeout(1.minute) do
  111. message_meta = @imap.fetch(message_id, ['RFC822.HEADER'])[0].attr
  112. end
  113. # check how many content messages we have, for notice used
  114. headers = parse_headers(message_meta['RFC822.HEADER'])
  115. next if messages_is_verify_message?(headers)
  116. next if messages_is_ignore_message?(headers)
  117. content_messages += 1
  118. break if content_max_check < content_messages
  119. end
  120. if content_messages >= content_max_check
  121. content_messages = message_ids.count
  122. end
  123. disconnect
  124. return {
  125. result: 'ok',
  126. content_messages: content_messages,
  127. }
  128. end
  129. # reverse message order to increase performance
  130. if check_type == 'verify'
  131. Rails.logger.info "verify mode, fetch no emails #{verify_string}"
  132. message_ids.reverse!
  133. # check for verify message
  134. message_ids.each do |message_id|
  135. message_meta = nil
  136. timeout(1.minute) do
  137. message_meta = @imap.fetch(message_id, ['ENVELOPE'])[0].attr
  138. end
  139. # check if verify message exists
  140. subject = message_meta['ENVELOPE'].subject
  141. next if !subject
  142. next if subject !~ /#{verify_string}/
  143. Rails.logger.info " - verify email #{verify_string} found"
  144. timeout(600) do
  145. @imap.store(message_id, '+FLAGS', [:Deleted])
  146. @imap.expunge()
  147. end
  148. disconnect
  149. return {
  150. result: 'ok',
  151. }
  152. end
  153. disconnect
  154. return {
  155. result: 'verify not ok',
  156. }
  157. end
  158. # fetch regular messages
  159. count_all = message_ids.count
  160. count = 0
  161. count_fetched = 0
  162. count_max = 5000
  163. active_check_interval = 20
  164. notice = ''
  165. message_ids.each do |message_id|
  166. count += 1
  167. if (count % active_check_interval).zero?
  168. break if channel_has_changed?(channel)
  169. end
  170. break if max_process_count_has_reached?(channel, count, count_max)
  171. Rails.logger.info " - message #{count}/#{count_all}"
  172. message_meta = nil
  173. timeout(1.minute) do
  174. message_meta = @imap.fetch(message_id, ['RFC822.SIZE', 'ENVELOPE', 'FLAGS', 'INTERNALDATE', 'RFC822.HEADER'])[0]
  175. end
  176. # ignore verify messages
  177. next if !messages_is_too_old_verify?(message_meta, count, count_all)
  178. # ignore to big messages
  179. info = too_big?(message_meta, count, count_all)
  180. if info
  181. notice += "#{info}\n"
  182. next
  183. end
  184. # ignore deleted messages
  185. next if deleted?(message_meta, count, count_all)
  186. # ignore already imported
  187. next if already_imported?(message_id, message_meta, count, count_all, keep_on_server, channel)
  188. # delete email from server after article was created
  189. msg = nil
  190. timeout(1.minute) do
  191. msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822']
  192. end
  193. next if !msg
  194. process(channel, msg, false)
  195. timeout(1.minute) do
  196. if !keep_on_server
  197. @imap.store(message_id, '+FLAGS', [:Deleted])
  198. else
  199. @imap.store(message_id, '+FLAGS', [:Seen])
  200. end
  201. end
  202. count_fetched += 1
  203. end
  204. if !keep_on_server
  205. timeout(10.minutes) do
  206. @imap.expunge()
  207. end
  208. end
  209. disconnect
  210. if count.zero?
  211. Rails.logger.info ' - no message'
  212. end
  213. Rails.logger.info 'done'
  214. {
  215. result: 'ok',
  216. fetched: count_fetched,
  217. notice: notice,
  218. }
  219. end
  220. def disconnect
  221. return if !@imap
  222. timeout(1.minute) do
  223. @imap.disconnect()
  224. end
  225. end
  226. =begin
  227. Channel::Driver::Imap.streamable?
  228. returns
  229. true|false
  230. =end
  231. def self.streamable?
  232. false
  233. end
  234. private
  235. def messages_is_too_old_verify?(message_meta, count, count_all)
  236. headers = parse_headers(message_meta.attr['RFC822.HEADER'])
  237. return true if !messages_is_verify_message?(headers)
  238. return true if headers['X-Zammad-Verify-Time'].blank?
  239. begin
  240. verify_time = Time.zone.parse(headers['X-Zammad-Verify-Time'])
  241. rescue => e
  242. Rails.logger.error e
  243. return true
  244. end
  245. return true if verify_time < Time.zone.now - 30.minutes
  246. Rails.logger.info " - ignore message #{count}/#{count_all} - because message has a verify message"
  247. false
  248. end
  249. def messages_is_verify_message?(headers)
  250. return true if headers['X-Zammad-Verify'] == 'true'
  251. false
  252. end
  253. def messages_is_ignore_message?(headers)
  254. return true if headers['X-Zammad-Ignore'] == 'true'
  255. false
  256. end
  257. def parse_headers(string)
  258. return {} if string.blank?
  259. headers = {}
  260. headers_pairs = string.split("\r\n")
  261. headers_pairs.each do |pair|
  262. key_value = pair.split(': ')
  263. next if key_value[0].blank?
  264. headers[key_value[0]] = key_value[1]
  265. end
  266. headers
  267. end
  268. =begin
  269. check if email is already impoted
  270. Channel::Driver::IMAP.already_imported?(message_id, message_meta, count, count_all, keep_on_server, channel)
  271. returns
  272. true|false
  273. =end
  274. # rubocop:disable Metrics/ParameterLists
  275. def already_imported?(message_id, message_meta, count, count_all, keep_on_server, channel)
  276. # rubocop:enable Metrics/ParameterLists
  277. return false if !keep_on_server
  278. return false if !message_meta.attr
  279. return false if !message_meta.attr['ENVELOPE']
  280. local_message_id = message_meta.attr['ENVELOPE'].message_id
  281. return false if local_message_id.blank?
  282. local_message_id_md5 = Digest::MD5.hexdigest(local_message_id)
  283. article = Ticket::Article.where(message_id_md5: local_message_id_md5).order('created_at DESC, id DESC').limit(1).first
  284. return false if !article
  285. # verify if message is already imported via same channel, if not, import it again
  286. ticket = article.ticket
  287. if ticket&.preferences && ticket.preferences[:channel_id].present? && channel.present?
  288. return false if ticket.preferences[:channel_id] != channel[:id]
  289. end
  290. timeout(1.minute) do
  291. @imap.store(message_id, '+FLAGS', [:Seen])
  292. end
  293. Rails.logger.info " - ignore message #{count}/#{count_all} - because message message id already imported"
  294. true
  295. end
  296. =begin
  297. check if email is already marked as deleted
  298. Channel::Driver::IMAP.deleted?(message_meta, count, count_all)
  299. returns
  300. true|false
  301. =end
  302. def deleted?(message_meta, count, count_all)
  303. return false if !message_meta.attr['FLAGS'].include?(:Deleted)
  304. Rails.logger.info " - ignore message #{count}/#{count_all} - because message has already delete flag"
  305. true
  306. end
  307. =begin
  308. check if email is to big
  309. Channel::Driver::IMAP.too_big?(message_meta, count, count_all)
  310. returns
  311. true|false
  312. =end
  313. def too_big?(message_meta, count, count_all)
  314. max_message_size = Setting.get('postmaster_max_size').to_f
  315. real_message_size = message_meta.attr['RFC822.SIZE'].to_f / 1024 / 1024
  316. if real_message_size > max_message_size
  317. info = " - ignore message #{count}/#{count_all} - because message is too big (is:#{real_message_size} MB/max:#{max_message_size} MB)"
  318. Rails.logger.info info
  319. return info
  320. end
  321. false
  322. end
  323. =begin
  324. check if channel config has changed
  325. Channel::Driver::IMAP.channel_has_changed?(channel)
  326. returns
  327. true|false
  328. =end
  329. def channel_has_changed?(channel)
  330. Rails.logger.info "CC #{channel.id} CHECK."
  331. current_channel = Channel.find_by(id: channel.id)
  332. if !current_channel
  333. Rails.logger.info "Channel with id #{channel.id} is deleted in the meantime. Stop fetching."
  334. return true
  335. end
  336. return false if channel.updated_at == current_channel.updated_at
  337. Rails.logger.info "Channel with id #{channel.id} has changed. Stop fetching."
  338. true
  339. end
  340. =begin
  341. check if maximal fetching email count has reached
  342. Channel::Driver::IMAP.max_process_count_has_reached?(channel, count, count_max)
  343. returns
  344. true|false
  345. =end
  346. def max_process_count_has_reached?(channel, count, count_max)
  347. return false if count < count_max
  348. Rails.logger.info "Maximal fetched emails (#{count_max}) reached for this interval for Channel with id #{channel.id}."
  349. true
  350. end
  351. def timeout(seconds)
  352. Timeout.timeout(seconds) do
  353. yield
  354. end
  355. end
  356. end