pop3.rb 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. require 'net/pop'
  3. class Channel::Driver::Pop3 < Channel::Driver::BaseEmailInbound
  4. FETCH_COUNT_MAX = 2_000
  5. OPEN_TIMEOUT = 16
  6. OPEN_CHECK_TIMEOUT = 4
  7. READ_TIMEOUT = 45
  8. READ_CHECK_TIMEOUT = 6
  9. # Fetches emails from POP3 server
  10. #
  11. # @param options [Hash]
  12. # @option options [String] :folder to fetch emails from
  13. # @option options [String] :user to login with
  14. # @option options [String] :password to login with
  15. # @option options [String] :host
  16. # @option options [Integer, String] :port
  17. # @option options [Boolean] :ssl_verify
  18. # @option options [String] :ssl off to turn off ssl
  19. # @param channel [Channel]
  20. #
  21. # @return [Hash]
  22. #
  23. # {
  24. # result: 'ok',
  25. # fetched: 123,
  26. # notice: 'e. g. message about to big emails in mailbox',
  27. # }
  28. #
  29. # @example
  30. #
  31. # params = {
  32. # user: 'xxx@zammad.onmicrosoft.com',
  33. # password: 'xxx',
  34. # host: 'example'com'
  35. # }
  36. #
  37. # channel = Channel.last
  38. # instance = Channel::Driver::Pop3.new
  39. # result = instance.fetch(params, channel)
  40. def fetch(...) # rubocop:disable Lint/UselessMethodDefinition
  41. # fetch() method is defined in superclass, but options are subclass-specific,
  42. # so define it here for documentation purposes.
  43. super
  44. end
  45. def disconnect
  46. return if !@pop
  47. @pop.finish
  48. end
  49. def setup_connection(options, check: false)
  50. ssl = true
  51. if options[:ssl] == 'off'
  52. ssl = false
  53. end
  54. ssl_verify = options.fetch(:ssl_verify, true)
  55. port = if options.key?(:port) && options[:port].present?
  56. options[:port].to_i
  57. elsif ssl == true
  58. 995
  59. else
  60. 110
  61. end
  62. Rails.logger.info "fetching pop3 (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})"
  63. @pop = ::Net::POP3.new(options[:host], port)
  64. # @pop.set_debug_output $stderr
  65. # on check, reduce open_timeout to have faster probing
  66. if check
  67. @pop.open_timeout = OPEN_CHECK_TIMEOUT
  68. @pop.read_timeout = READ_CHECK_TIMEOUT
  69. else
  70. @pop.open_timeout = OPEN_TIMEOUT
  71. @pop.read_timeout = READ_TIMEOUT
  72. end
  73. if ssl
  74. Certificate::ApplySSLCertificates.ensure_fresh_ssl_context
  75. @pop.enable_ssl((ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE))
  76. end
  77. @pop.start(options[:user], options[:password])
  78. end
  79. def messages_iterator(_keep_on_server, _options, reverse: false)
  80. all = @pop.mails
  81. all.reverse! if reverse
  82. [all.first(FETCH_COUNT_MAX), all.size]
  83. end
  84. def fetch_single_message(message, count, count_all)
  85. mail = message.pop
  86. return MessageResult.new(success: false) if !mail
  87. message_validator = MessageValidator.new(self.class.extract_headers(mail), mail.size)
  88. if message_validator.fresh_verify_message?
  89. Rails.logger.info " - ignore message #{count}/#{count_all} - because message has a verify message"
  90. return MessageResult.new(sucess: false)
  91. end
  92. # do not process too large messages, instead download and send postmaster reply
  93. if (too_large_info = message_validator.too_large?)
  94. if Setting.get('postmaster_send_reject_if_mail_too_large') == true
  95. 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)"
  96. Rails.logger.info info
  97. after_action = [:notice, "#{info}\n"]
  98. process_oversized_mail(@channel, mail)
  99. else
  100. info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
  101. Rails.logger.info info
  102. return MessageResult.new(success: false, after_action: [:too_large_ignored, "#{info}\n"])
  103. end
  104. else
  105. process(@channel, message.pop, false)
  106. end
  107. message.delete
  108. MessageResult.new(success: true, after_action: after_action)
  109. end
  110. def fetch_wrap_up
  111. disconnect
  112. end
  113. def check_single_message(message_id)
  114. mail = message_id.pop
  115. return if !mail
  116. MessageValidator.new(self.class.extract_headers(mail), mail.size)
  117. end
  118. def verify_single_message(message_id, verify_regexp)
  119. mail = message_id.pop
  120. return if !mail
  121. # check if verify message exists
  122. mail.match?(verify_regexp)
  123. end
  124. def verify_message_cleanup(message_id)
  125. message_id.delete
  126. end
  127. def self.extract_headers(mail)
  128. {
  129. 'X-Zammad-Verify' => mail.include?('X-Zammad-Ignore: true') ? 'true' : 'false',
  130. 'X-Zammad-Ignore' => mail.include?('X-Zammad-Verify: true') ? 'true' : 'false',
  131. 'X-Zammad-Verify-Time' => mail.match(%r{X-Zammad-Verify-Time:\s(.+?)\s})&.captures&.first,
  132. }.with_indifferent_access
  133. end
  134. end