require "net/imap" require "mail" require "time" require 'base64' require 'fileutils' require 'yaml' require "logger" require "httparty" require "http_service/naviai" require 'openssl' ## # This module provides the common functionality that will be needed for local and cloud module. # @client_type defines if the gem is being used for local-lockbox or cloud-version. # module Client @logglyTag def logger @logger end def errors @errors end # # imap_connection # # connect the app with imap server # def imap_connection(server, username, password, exitOnFail = true) # connect to IMAP server imap = Net::IMAP.new server, ssl: true, certs: nil, verify: false Net::IMAP.debug = @net_imap_debug # http://ruby-doc.org/stdlib-2.1.5/libdoc/net/imap/rdoc/Net/IMAP.html#method-i-capability capabilities = imap.capability @logger.debug("imap capabilities: #{capabilities.join(',')}") if @debug if @client_type == 'local' unless capabilities.include? "IDLE" || @debug @logger.info "'IDLE' IMAP capability not available in server: #{server}" imap.disconnect exit if exitOnFail end end begin # login imap.login username, password rescue Net::IMAP::NoResponseError => e # mostly due to credentials error @errors = {exception: e} rescue Net::IMAP::BadResponseError => e @errors = {exception: e} rescue @errors = {exception: e} end # return IMAP connection handler imap end # # retrieve_emails # # retrieve any mail from a folder, followin specified serach condition # for any mail retrieved call a specified block # def retrieve_emails(imap, search_condition, folder, &process_email_block) # examine will read email and sets flags unread # https://stackoverflow.com/questions/16516464/read-gmail-xoauth-mails-without-marking-it-read imap.examine folder message_ids = imap.uid_search(search_condition) meta_dir = @download_path + 'meta/' message_ids_saved = getMessageUUIds(meta_dir) message_ids = message_ids - message_ids_saved if @debug if message_ids.empty? @logger.info "No new emails found..." elsif message_ids_saved.any? @logger.info "Found emails saved in your #{@client_type}. Downloading only #{message_ids.count} new emails..." else @logger.info "Downloading #{message_ids.count} emails." end end message_ids.each_with_index do |message_id, i| begin # fetch all the email contents data = imap.uid_fetch(message_id, "RFC822") data.each do |d| msg = d.attr['RFC822'] # instantiate a Mail object to avoid further IMAP parameters nightmares mail = Mail.read_from_string msg # call the block with mail object as param process_email_block.call mail, i, i == message_ids.length-1, message_id # mark as read if @mark_as_read imap.store(message_id, "+FLAGS", [:Seen]) end end rescue => e unless logger.nil? logger.info "Issue processing email for uuid ##{message_id}, #{e.message}" end logToLoggly({event:"EMAIL_SYNC_FAILED", env: @env, storage: @client_type, email: @current_user_email, uuid: message_id, error: e.message}) raise e end end end ## # Process each email downloaded from imap-server and creates meta file that will be sent over to the navi-ai service. # Meta will content information like: ['from', 'to', 'cc', ..., 'body_url']. This information is then used by navi-ai to parse the body content. # def process_email(mail, uid, stamp = nil) meta = Hash.new custom_uid = (Time.now.to_f * 1000).to_s + "_" + mail.__id__.to_s meta["from"] = mail[:from].to_s meta["to"] = mail[:to].to_s meta["cc"] = mail[:cc].to_s meta["subject"] = mail.subject meta["date"] = mail.date.to_s if mail.multipart? for i in 0...mail.parts.length m = download(mail.parts[i], custom_uid) meta.merge!(m) unless m.nil? end else m = download(mail, custom_uid) meta.merge!(m) unless m.nil? end if stamp.nil? save(meta, "meta/#{uid.to_s + '_' + custom_uid}") else save(meta, "meta/#{uid.to_s + '_' + custom_uid}", stamp) end end def encrypt(data) Base64.encode64(data) end def depcrecated_encrypt(data) cipher = OpenSSL::Cipher::AES.new(256, :CFB) cipher.encrypt yml_config = config key_iv_exists = (yml_config['key'] && yml_config['iv']) ? (!yml_config['key'].empty? && !yml_config['iv'].empty? ) : false if key_iv_exists # this condition must be true for cloud version cipher.key = Base64.decode64(File.read(yml_config['key'])) cipher.iv = Base64.decode64(File.read(yml_config['iv'])) else cipher.key = key = cipher.random_key cipher.iv = iv = cipher.random_iv key_path, iv_path = save_aes_key_iv(key, iv) yml_config['key'] = key_path yml_config['iv'] = iv_path update_config(yml_config) end encrypted = cipher.update(data) Base64.encode64(encrypted) end def time_now Time.now.utc.iso8601(3) end ## # If the gem is being used for local-lockbox mode, after fetching all emails, the process will go idle mode. If user send the interrupt command, it will call shutdown to disconnect the connection with imap server # def shutdown(imap) imap.idle_done imap.logout unless imap.disconnected? imap.disconnect @logger.info "#{$0} has ended (crowd applauds)" exit 0 end def logToLoggly(messageBody) if(@logglyTag) begin HTTParty.post("http://logs-01.loggly.com/bulk/0d67f93b-6568-4b00-9eca-97d0ea0bd5a1/tag/#{@logglyTag}/", body: messageBody.to_json, headers: { 'Content-Type' => 'application/json' } ) rescue true end else if @debug && !@logger.nil? @logger.info "Logging to Loggly disabled" @logger.info messageBody end end end def setupLoggly(tag) @logglyTag = tag end end