123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
- require 'net/pop'
- class Channel::Driver::Pop3 < Channel::Driver::BaseEmailInbound
- FETCH_COUNT_MAX = 2_000
- OPEN_TIMEOUT = 16
- OPEN_CHECK_TIMEOUT = 4
- READ_TIMEOUT = 45
- READ_CHECK_TIMEOUT = 6
- # Fetches emails from POP3 server
- #
- # @param options [Hash]
- # @option options [String] :folder to fetch emails from
- # @option options [String] :user to login with
- # @option options [String] :password to login with
- # @option options [String] :host
- # @option options [Integer, String] :port
- # @option options [Boolean] :ssl_verify
- # @option options [String] :ssl off to turn off ssl
- # @param channel [Channel]
- #
- # @return [Hash]
- #
- # {
- # result: 'ok',
- # fetched: 123,
- # notice: 'e. g. message about to big emails in mailbox',
- # }
- #
- # @example
- #
- # params = {
- # user: 'xxx@zammad.onmicrosoft.com',
- # password: 'xxx',
- # host: 'example'com'
- # }
- #
- # channel = Channel.last
- # instance = Channel::Driver::Pop3.new
- # result = instance.fetch(params, channel)
- def fetch(...) # rubocop:disable Lint/UselessMethodDefinition
- # fetch() method is defined in superclass, but options are subclass-specific,
- # so define it here for documentation purposes.
- super
- end
- def disconnect
- return if !@pop
- @pop.finish
- end
- def setup_connection(options, check: false)
- 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
- if check
- @pop.open_timeout = OPEN_CHECK_TIMEOUT
- @pop.read_timeout = READ_CHECK_TIMEOUT
- else
- @pop.open_timeout = OPEN_TIMEOUT
- @pop.read_timeout = READ_TIMEOUT
- 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])
- end
- def messages_iterator(_keep_on_server, _options, reverse: false)
- all = @pop.mails
- all.reverse! if reverse
- [all.first(FETCH_COUNT_MAX), all.size]
- end
- def fetch_single_message(message, count, count_all)
- mail = message.pop
- return MessageResult.new(success: false) if !mail
- message_validator = MessageValidator.new(self.class.extract_headers(mail), mail.size)
- if message_validator.fresh_verify_message?
- Rails.logger.info " - ignore message #{count}/#{count_all} - because message has a verify message"
- return MessageResult.new(sucess: false)
- end
- # do not process too large messages, instead download and send postmaster reply
- if (too_large_info = message_validator.too_large?)
- 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:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
- Rails.logger.info info
- after_action = [:notice, "#{info}\n"]
- process_oversized_mail(@channel, mail)
- else
- info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
- Rails.logger.info info
- return MessageResult.new(success: false, after_action: [:too_large_ignored, "#{info}\n"])
- end
- else
- process(@channel, message.pop, false)
- end
- message.delete
- MessageResult.new(success: true, after_action: after_action)
- end
- def fetch_wrap_up
- disconnect
- end
- def check_single_message(message_id)
- mail = message_id.pop
- return if !mail
- MessageValidator.new(self.class.extract_headers(mail), mail.size)
- end
- def verify_single_message(message_id, verify_regexp)
- mail = message_id.pop
- return if !mail
- # check if verify message exists
- mail.match?(verify_regexp)
- end
- def verify_message_cleanup(message_id)
- message_id.delete
- end
- def self.extract_headers(mail)
- {
- 'X-Zammad-Verify' => mail.include?('X-Zammad-Ignore: true') ? 'true' : 'false',
- 'X-Zammad-Ignore' => mail.include?('X-Zammad-Verify: true') ? 'true' : 'false',
- 'X-Zammad-Verify-Time' => mail.match(%r{X-Zammad-Verify-Time:\s(.+?)\s})&.captures&.first,
- }.with_indifferent_access
- end
- end
|