123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'net/pop'
- class Channel::Driver::Pop3 < Channel::EmailParser
- =begin
- fetch emails from Pop3 account
- instance = Channel::Driver::Pop3.new
- result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for)
- returns
- {
- result: 'ok',
- fetched: 123,
- notice: 'e. g. message about to big emails in mailbox',
- }
- check if connect to Pop3 account is possible, return count of mails in mailbox
- instance = Channel::Driver::Pop3.new
- result = instance.fetch(params[:inbound][:options], channel, 'check')
- returns
- {
- result: 'ok',
- content_messages: 123,
- }
- verify Pop3 account, check if search email is in there
- instance = Channel::Driver::Pop3.new
- result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for)
- returns
- {
- result: 'ok', # 'verify not ok'
- }
- =end
- def fetch(options, channel, check_type = '', verify_string = '')
- ssl = true
- if options[:ssl] == 'off'
- ssl = false
- end
- ssl_verify = options.fetch(:ssl_verify, true)
- port = if options.key?(:port) && options[:port].present?
- options[:port].to_i
- elsif ssl == true
- 995
- else
- 110
- end
- Rails.logger.info "fetching pop3 (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})"
- @pop = ::Net::POP3.new(options[:host], port)
- # @pop.set_debug_output $stderr
- # on check, reduce open_timeout to have faster probing
- @pop.open_timeout = 16
- @pop.read_timeout = 45
- if check_type == 'check'
- @pop.open_timeout = 4
- @pop.read_timeout = 6
- end
- if ssl
- Certificate::ApplySSLCertificates.ensure_fresh_ssl_context
- @pop.enable_ssl((ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE))
- end
- @pop.start(options[:user], options[:password])
- mails = @pop.mails
- if check_type == 'check'
- Rails.logger.info 'check only mode, fetch no emails'
- content_max_check = 2
- content_messages = 0
- # check messages
- mails.each do |m|
- mail = m.pop
- next if !mail
- # check how many content messages we have, for notice used
- if !mail.match?(%r{(X-Zammad-Ignore: true|X-Zammad-Verify: true)})
- content_messages += 1
- break if content_max_check < content_messages
- end
- end
- if content_messages >= content_max_check
- content_messages = mails.count
- end
- disconnect
- return {
- result: 'ok',
- content_messages: content_messages,
- }
- end
- # reverse message order to increase performance
- if check_type == 'verify'
- Rails.logger.info 'verify mode, fetch no emails'
- mails.reverse!
- # check for verify message
- mails.first(2000).each do |m|
- mail = m.pop
- next if !mail
- # check if verify message exists
- next if !mail.match?(%r{#{verify_string}})
- Rails.logger.info " - verify email #{verify_string} found"
- m.delete
- disconnect
- return {
- result: 'ok',
- }
- end
- return {
- result: 'verify not ok',
- }
- end
- # fetch regular messages
- count_all = mails.size
- count = 0
- count_fetched = 0
- too_large_messages = []
- active_check_interval = 20
- notice = ''
- mails.first(2000).each do |m|
- count += 1
- break if (count % active_check_interval).zero? && channel_has_changed?(channel)
- Rails.logger.info " - message #{count}/#{count_all}"
- mail = m.pop
- next if !mail
- # ignore verify messages
- if mail.match?(%r{(X-Zammad-Ignore: true|X-Zammad-Verify: true)}) && mail =~ %r{X-Zammad-Verify-Time:\s(.+?)\s}
- begin
- verify_time = Time.zone.parse($1)
- if verify_time > 30.minutes.ago
- info = " - ignore message #{count}/#{count_all} - because it's a verify message"
- Rails.logger.info info
- next
- end
- rescue => e
- Rails.logger.error e
- end
- end
- # do not process too large messages, instead download and send postmaster reply
- max_message_size = Setting.get('postmaster_max_size').to_f
- real_message_size = mail.size.to_f / 1024 / 1024
- if real_message_size > max_message_size
- if Setting.get('postmaster_send_reject_if_mail_too_large') == true
- info = " - download message #{count}/#{count_all} - ignore message because it's too large (is:#{real_message_size} MB/max:#{max_message_size} MB)"
- Rails.logger.info info
- notice += "#{info}\n"
- process_oversized_mail(channel, mail)
- else
- info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{real_message_size} MB/max:#{max_message_size} MB)"
- Rails.logger.info info
- notice += "#{info}\n"
- too_large_messages.push info
- next
- end
- # delete email from server after article was created
- else
- process(channel, m.pop, false)
- end
- m.delete
- count_fetched += 1
- end
- disconnect
- if count.zero?
- Rails.logger.info ' - no message'
- end
- if too_large_messages.present?
- raise too_large_messages.join("\n")
- end
- Rails.logger.info 'done'
- {
- result: 'ok',
- fetched: count_fetched,
- notice: notice,
- }
- end
- =begin
- instance = Channel::Driver::Pop3.new
- instance.fetchable?(channel)
- =end
- def fetchable?(_channel)
- true
- end
- =begin
- Channel::Driver::Pop3.streamable?
- returns
- true|false
- =end
- def self.streamable?
- false
- end
- =begin
- check if channel config has changed
- Channel::Driver::IMAP.channel_has_changed?(channel)
- returns
- true|false
- =end
- def channel_has_changed?(channel)
- current_channel = Channel.find_by(id: channel.id)
- if !current_channel
- Rails.logger.info "Channel with id #{channel.id} is deleted in the meantime. Stop fetching."
- return true
- end
- return false if channel.updated_at == current_channel.updated_at
- Rails.logger.info "Channel with id #{channel.id} has changed. Stop fetching."
- true
- end
- def disconnect
- return if !@pop
- @pop.finish
- end
- end
|