Esempio n. 1
0
class GmailImapClient(object):
    """Imap client with some specific methods for working with gmail"""

    IMAP_SERVER = "imap.gmail.com"
    IMAP_SERVER_PORT = "993"

    def __init__(self, email_address, password):
        self.client = IMAPClient(self.IMAP_SERVER, use_uid=True, ssl=True)
        self.email_address = email_address
        self.password = password
        self.messages_for_this_session = []
        self._login()

    def search(self, from_address, to_address, subject,
               since=datetime.utcnow()-timedelta(minutes=1)):
        """Search for emails on an IMAP server"""

        return self.emails_from_messages(
            self.client.search(
                [
                    'FROM "%s"' % (from_address,),
                    'TO "%s"' % (to_address,),
                    'SUBJECT "%s"' % (subject,),
                    'SINCE %s' % (since.strftime('%d-%b-%Y'),),
                ],
            )
        )

    def _login(self):
        """Login to imap server"""
        self.client.login(self.email_address, self.password)
        self.client.select_folder("INBOX")

    def delete_seen_messages(self):
        """Delete messages that have been accessed with this client"""
        self.client.delete_messages(self.messages_for_this_session)
        self.client.expunge()

    def gmail_search(self, query):
        """Search the gmail imap server using gmail queries"""
        self.client.logout()
        self.client = IMAPClient(self.IMAP_SERVER, use_uid=True, ssl=True)
        self._login()
        # Can use full gmail queries like 'has:attachment in:unread'
        messages = self.client.gmail_search(query)
        self.messages_for_this_session.append(messages)
        # We recreate a whole connection after querying gmail because
        # otherwise it caches our search results forever
        self.client.logout()
        self.client = IMAPClient(self.IMAP_SERVER, use_uid=True, ssl=True)
        self._login()
        return self.emails_from_messages(messages)

    def emails_from_messages(self, messages):
        """Convert a list of IMAP messages into email objects"""
        response = self.client.fetch(messages, ["RFC822"])
        return [
            email.message_from_string(data["RFC822"])
            for _, data in response.items()
        ]
Esempio n. 2
0
def DeleteMail():
    client = IMAPClient('imap.googlemail.com', use_uid=True, ssl=True)
    client.login(FROM, Decode(PASSMAIL))
    folders = [
        'INBOX', '[Gmail]/Drafts', '[Gmail]/Important', '[Gmail]/Sent Mail',
        '[Gmail]/Spam', '[Gmail]/Starred', '[Gmail]/Trash'
    ]

    #for f in folders:
    fold = client.select_folder(folders[3])
    print(client.search())
    res = client.delete_messages(client.search())
    res = client.expunge()
    client.close_folder()

    fold = client.select_folder(folders[6])
    print(client.search())
    res = client.delete_messages(client.search())
    res = client.expunge()
    client.close_folder()

    #Google automatically will move deleted messages to "All Mail" folder.
    #Now we can remove all messages from "All Mail"

    client.select_folder("[Gmail]/All Mail")
    client.set_gmail_labels(client.search(), '\\Trash')
    client.delete_messages(client.search())
    client.expunge()
    client.logout()
    print("Sent mails are deleted!")
Esempio n. 3
0
class GmailImapClient(object):
    """Imap client with some specific methods for working with gmail"""

    IMAP_SERVER = "imap.gmail.com"
    IMAP_SERVER_PORT = "993"

    def __init__(self, email_address, password):
        self.client = IMAPClient(self.IMAP_SERVER, use_uid=True, ssl=True)
        self.email_address = email_address
        self.password = password
        self.messages_for_this_session = []
        self._login()

    def search(self,
               from_address,
               to_address,
               subject,
               since=datetime.utcnow() - timedelta(minutes=1)):
        """Search for emails on an IMAP server"""

        return self.emails_from_messages(
            self.client.search([
                'FROM "%s"' % (from_address, ),
                'TO "%s"' % (to_address, ),
                'SUBJECT "%s"' % (subject, ),
                'SINCE %s' % (since.strftime('%d-%b-%Y'), ),
            ], ))

    def _login(self):
        """Login to imap server"""
        self.client.login(self.email_address, self.password)
        self.client.select_folder("INBOX")

    def delete_seen_messages(self):
        """Delete messages that have been accessed with this client"""
        self.client.delete_messages(self.messages_for_this_session)
        self.client.expunge()

    def gmail_search(self, query):
        """Search the gmail imap server using gmail queries"""
        self.client.logout()
        self.client = IMAPClient(self.IMAP_SERVER, use_uid=True, ssl=True)
        self._login()
        # Can use full gmail queries like 'has:attachment in:unread'
        messages = self.client.gmail_search(query)
        self.messages_for_this_session.append(messages)
        # We recreate a whole connection after querying gmail because
        # otherwise it caches our search results forever
        self.client.logout()
        self.client = IMAPClient(self.IMAP_SERVER, use_uid=True, ssl=True)
        self._login()
        return self.emails_from_messages(messages)

    def emails_from_messages(self, messages):
        """Convert a list of IMAP messages into email objects"""
        response = self.client.fetch(messages, ["RFC822"])
        return [
            email.message_from_string(data["RFC822"])
            for _, data in response.items()
        ]
Esempio n. 4
0
    def parse(self):
        use_ssl = True if self.config["EMAIL"]["useSSL"] else False
        server = IMAPClient(self.config["EMAIL"]["servername"], ssl=use_ssl)
        server.login(self.config["EMAIL"]["username"], self.config["EMAIL"]["password"])
        logging.getLogger().debug("connected to IMAP server")

        select_info = server.select_folder("INBOX")
        # get list of fitting messages
        messages = server.search(["NOT", "DELETED", "SUBJECT", self.config["EMAIL"]["subject"]])
        logging.getLogger().info("%d email message(s) found" % len(messages))

        # loop through all messages
        for msgid in messages:
            # download it
            response = server.fetch(msgid, ["RFC822"])
            msg = email.message_from_bytes(response[msgid][b"RFC822"])
            self.__process_message(msg)

        # delete messages?
        if len(messages) > 0 and int(self.config["EMAIL"]["deleteAfterProcessing"]):
            if int(self.config["EMAIL"]["deleteAfterProcessing"]) > 1:
                messages = messages[:-1]
            server.delete_messages(messages)
            if self.config["EMAIL"]["expungeMailbox"]:
                server.expunge()
            logging.getLogger().info("Deleted email message(s) from server")

        server.logout()
Esempio n. 5
0
def DeleteMail():
    client = IMAPClient('imap.googlemail.com', use_uid=True, ssl=True)
    client.login(FROM, Decode(PASSMAIL))
    folders = [
        'INBOX',
        '[Gmail]/Drafts',
        '[Gmail]/Important',
        '[Gmail]/Sent Mail',
        '[Gmail]/Spam',
        '[Gmail]/Starred',
        '[Gmail]/Trash'
    ]

    #for f in folders:
    fold = client.select_folder(folders[3])
    print(client.search())
    res = client.delete_messages(client.search())
    res = client.expunge()
    client.close_folder()

    fold = client.select_folder(folders[6])
    print(client.search())
    res = client.delete_messages(client.search())
    res = client.expunge()
    client.close_folder()

    #Google automatically will move deleted messages to "All Mail" folder.
    #Now we can remove all messages from "All Mail"

    client.select_folder("[Gmail]/All Mail")
    client.set_gmail_labels(client.search(), '\\Trash')
    client.delete_messages(client.search())
    client.expunge()
    client.logout()
    print("Sent mails are deleted!")
Esempio n. 6
0
    def parse(self):
        server = IMAPClient(self.config['EMAIL']['servername'])
        server.login(self.config['EMAIL']['username'], self.config['EMAIL']['password'])
        logging.getLogger().debug("connected to IMAP server")

        select_info = server.select_folder('INBOX')
        # get list of fitting messages
        messages = server.search(['NOT DELETED',
                                  'SUBJECT "' + self.config['EMAIL']['subject'] + '"'])
        logging.getLogger().info("%d email message(s) found" % len(messages))

        # loop through all messages
        for msgid in messages:
            # download it
            response = server.fetch(msgid, ['RFC822'])
            msg = email.message_from_bytes(response[msgid][b'RFC822'])
            self.__process_message(msg)

        # delete messages?
        if len(messages) > 0 and int(self.config['EMAIL']['deleteAfterProcessing']):
            if int(self.config['EMAIL']['deleteAfterProcessing']) > 1:
                messages = messages[:-1]
            server.delete_messages(messages)
            if self.config['EMAIL']['expungeMailbox']:
                server.expunge()
            logging.getLogger().info("Deleted email message(s) from server")

        server.logout()
Esempio n. 7
0
def random_mail(logged_in_client: IMAPClient, config):
    ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
    ssl_context.verify_flags = ssl.CERT_OPTIONAL

    # send message
    server: smtplib.SMTP = smtplib.SMTP(config["hostname"],
                                        config["smtp_port"])
    if config["ssl"]:
        server.starttls(context=ssl_context)

    next_uid = logged_in_client.select_folder("INBOX",
                                              readonly=True)[b"UIDNEXT"]
    server.login("*****@*****.**", "test0")
    message = """\\
    Subject: Hi there

    This message is sent from Python."""
    server.sendmail("*****@*****.**", "*****@*****.**", message)

    wait_till_mail_appears(logged_in_client, uid=next_uid)

    yield next_uid

    logged_in_client.select_folder(EmailFolders.INBOX)
    logged_in_client.delete_messages(next_uid)
    logged_in_client.expunge()
def check_photo_request(hostname, username, password, boss):
    from imapclient import IMAPClient
    import email
    from email.utils import parseaddr

    server = IMAPClient(hostname, use_uid=True, ssl=True)
    server.login(username, password)
    select_info = server.select_folder('Inbox')
    msg_count = select_info[b'EXISTS']
    if msg_count == 0:
        return False
    command_photo = False
    messages = server.search()
    for msgid, data in server.fetch(messages, [b'RFC822']).items():
        msg = email.message_from_bytes(data[b'RFC822'])
        subject = str(msg['Subject'])
        mfrom = str(parseaddr(msg['From'])[1])
        if 'photo' == subject.strip().lower() and boss == mfrom.strip().lower(
        ):
            command_photo = True
            break
    server.delete_messages(messages, silent=True)
    server.expunge(messages)
    server.logout()
    return command_photo
Esempio n. 9
0
def main():
    mail = IMAPClient(config['mail']['host'], use_uid=True)
    mail.login(config['mail']['user'], config['mail']['password'])
    mail.select_folder('INBOX')
    messages = mail.search(['ALL'])
    if len(messages) > 0:
        response = mail.fetch(messages, ['BODY.PEEK[HEADER.FIELDS (TO)]', 'RFC822'])
        mandrill_client = mandrill.Mandrill(config['mandrill']['password'])
        for msgnum, data in response.items():
            do_forward(mandrill_client, data[b'BODY[HEADER.FIELDS (TO)]'], data[b'RFC822'])
        mail.delete_messages(messages)
        mail.expunge()
        return True
    else:
        print('No messages')
        return False
Esempio n. 10
0
class ReadManager:
    def __init__(self, config_dict):
        self.conn = IMAPClient(host="imap.gmail.com")
        self.conn.login(config_dict['incoming username'],
                        config_dict['incoming password'])
        self.conn.select_folder('INBOX')

    def check_emails(self, who_from):
        new_messages = self.conn.search([['FROM', who_from]])
        emails = []
        for msgid, raw_email in self.conn.fetch(new_messages,
                                                'RFC822').items():
            emails.append(ReadEmail(msgid, raw_email))

        self.conn.delete_messages(new_messages)
        self.conn.expunge()
        self.conn.noop()

        return emails

    def logout(self):
        self.conn.logout()
def delete_mail(event, context):
    # TODO implement
    email_id = os.environ['Email_ID']
    mail_list = MailList()
    mail_list = mail_list.spam_mail_list
    obj = IMAPClient('imap.gmail.com', ssl=True)
    obj.login(email_id, DECRYPTED)
    select_info = obj.select_folder('Inbox')
    mails_before_deletion = select_info[b'EXISTS']
    print('%d mails in INBOX before deletion' % mails_before_deletion)
    for i in mail_list:
        msg_ids = obj.search(['FROM', i])
        if (len(msg_ids)) != 0:
            print('%d mails deleted from %s' % (len(msg_ids), i))
        obj.delete_messages(msg_ids)
    select_info1 = obj.select_folder('Inbox')
    mails_after_deletion = select_info1[b'EXISTS']
    print('%d mails in INBOX after deletion' % mails_after_deletion)
    print('Total number of mails deleted : %d' %
          (mails_before_deletion - mails_after_deletion))
    obj.expunge()
    obj.logout()
    return {'statusCode': 200, 'body': msg_ids}
Esempio n. 12
0
            print(
                "#################################################################"
            )
            # Copy my_digesters_setup_template.py to the my_digesters_setup.py,
            # if you're wanting to customize the digesters.
            from my_digesters_setup_sample import add_digesters

        # Get Digesters from my_digesters_setup.py
        add_digesters(digesters)

        DigestionProcessor(notification_folder, digest_folder, digesters, options.print_summary,
                           options.sender_to_implicate, options.move_unmatched, options.digest_folder_name)\
            .doit()

    try:
        digest_folder.expunge()
    except IMAPClient.AbortError as e:
        print("Error expunging digest folder:")
        e.print_exc()

    digest_folder.logout()

    try:
        notification_folder.expunge()
    except IMAPClient.AbortError as e:
        print("Error expunging notification folder")
    notification_folder.logout()

    if command is "BASH-OPERATIONS":
        sys.exit(202)  ## HTTP 'accepted' (FYI)
Esempio n. 13
0
        subject = email_message.get('Subject')
        print(sender, ' ', subject)

        #If we find the email, we stop looking and mark NJ as being completed
        if sender.find(TargetEmailAddress) != -1 and subject.find(
                TargetEmailSubject) != -1:
            print('Found the specified email', subject)
            EmailWasFound = True
            break

        else:
            print('Not a Match')

    #NOW WE DELETE ALL EMAILS - we do this so the next time we search, the email alert will not already be in there.
    server.delete_messages(messages)
    server.expunge()

    #We close the connection and disconnect from the IMAP server
    server.logout()

#We've failed checking for emails
except BaseException as e:
    print(e)
    #Send an email saying this failed and then exit
    sendcheckfailemail()
    sys.exit(1)

#If we received the email, we exit and do nothing
if EmailWasFound:
    print('Yay, We found it!')
Esempio n. 14
0
class CuckooRequest(object):

    def __init__(self, message):
        self.message = message
        
        '''cuckooinbox config variables'''
        config = Config(cfg=os.path.join(CUCKOO_ROOT,"cuckooinbox","cuckooinbox.conf"))
        config = config.get('cuckooinbox')
        self.username = config['username']
        self.passwd = config['passwd']
        self.imap = config['imap']
        self.imap_ssl = config['imap_ssl']
        self.smtp_server = config['smtp']
        self.interval = config['interval']
        self.archive_folder = config['archive_folder']
        self.email_whitelist = config['email_whitelist']
        self.url_limit = config['url_limit']
        self.attachment_limit = config['attachment_limit'] 
        self.zip_reports = config['zip_reports']
        self.zip_password = config['zip_password']
        self.url_blacklist = config['url_blacklist']
        self.url_file_backlist = config['url_file_backlist']
        self.machine = config['machine']
        
        '''imap variables'''
        self.server = IMAPClient(self.imap, use_uid=True, ssl=self.imap_ssl)
        self.server.login(self.username, self.passwd)
        self.attachment_counter = 0
        
        '''message variables'''
        self.msg = MIMEMultipart()
        self.response_msg = MIMEMultipart()
        self.response_urls = []
        self.response_attachments = []
        self.sender = ''
        self.subject = ''
        self.cc_list = []

        '''logging object'''
        self.log_entry = Logger('cuckooinbox.log')

        '''cuckoo variables'''
        self.taskids = []
        self.db  = Database()
        self.url_counter = 0 # tracks url count to not exceed url_limit
    
    def fetch(self, message):
        
        '''set retrieve folder'''
        select_info = self.server.select_folder('INBOX')
        '''fetch mail'''
        response = self.server.fetch(self.message, ['RFC822'])
    
        '''parse received email'''
        for msgid, data in response.iteritems():
            msg_string = data['RFC822']
            self.msg = email.message_from_string(msg_string)
            
            '''parse 'Name <*****@*****.**>' format'''
            if '<' in self.msg['From']: self.sender = self.msg['From'].split('<'[0])[-1][:-1]            
            else: self.sender = self.msg['From']
            self.subject = self.msg['Subject']
            
            '''print and log successful receive'''
            self.log_entry.logEvent('[+] Received email ID: %d from %s [%s]' % (msgid, self.msg['From'], self.msg['Subject']))
            
            '''save CC info for reply later'''
            if self.msg['Cc']:
                for address in  self.msg['Cc'].split(', '): self.cc_list.append(address)
                self.log_entry.logEvent( '[*] Email \"%s\" from %s cc\'d the following addresses: %s' % (self.msg['Subject'],self.msg['From'],', '.join(str(copies) for copies in self.cc_list)))
            
            file_whitelist = ['exe', 'doc', 'docx', 'xls', 'xlsx', 'pdf', 'zip']
            
            '''parse message elements'''
            for part in self.msg.walk():
                if part.get_content_type() == 'text/plain':
                    self.log_entry.logEvent( '[*] Email ID: %d has a plain text object.' % msgid)
                    content = part.get_payload()
                    self.processText(content)
                elif part.get_content_type() == 'text/html':
                    self.log_entry.logEvent('[*] Email ID: %d has a html object.' % msgid)
                    content = part.get_payload()
                    self.processText(content)
                elif 'application' in part.get_content_type():
                    # email attachment has no filename
                    if not part.get_param('name'): return 0
                    # cuckoo file analysis whitelist
                    if not part.get_param('name').split('.'[0])[-1] in file_whitelist: break
                    # increment and break if limit is reached 
                    if (self.attachment_limit != 0 and self.attachment_counter == self.attachment_limit): break
                    self.attachment_counter += 1
                    self.log_entry.logEvent('[*] Email ID: %d has an attachment object.' % msgid)
                    content = part.get_payload()
                    file_name = part.get_param('name')
                    self.processAttachment(content, file_name)
                    
            '''archive email when done submitting cuckoo tasks'''
            try:
                self.archive(self.message)
            except:
                self.server.delete_messages(self.message)
                self.server.expunge()

    def processText(self, content):
        
        '''reformat quoted string mail to plain html'''
        body = quopri.decodestring(content)
        soup = BeautifulSoup(body)
        # todo analyze href spoof
        
        '''parse and analyze hyperlinks'''
        for url in soup.findAll('a'):
            # strip mailto links
            if url['href'].split(':'[0])[0] == 'mailto' : continue
            # strip blacklist links
            for item in self.url_blacklist.split(','):
                if item in url['href']: return 0
            # strip blacklist link filetypes
            for item in self.url_file_backlist.split(','):
                if item in url['href'].split('.'[0])[-1]: return 0
            else:
                self.response_urls.append(url['href'])
		if self.machine:
                    task_id = self.db.add_url(url['href'], package="ie", timeout=15, machine=self.machine)
                else: task_id = self.db.add_url(url['href'], package="ie", timeout=15)
                if task_id:
                    self.taskids.append(task_id)
                    self.log_entry.logEvent('[+] URL \"%s\" added as task with ID %d' % (url['href'],task_id))
                    # increment counter and exit loop if limit is reached
                    self.url_counter += 1
                    if (self.url_limit != 0 and self.url_counter == self.url_limit): return 0
                else:
                    self.log_entry.logEvent("[!] Error: adding task to database" % (url['href'],task_id))
                    break

    def processAttachment(self, content, filename):
        '''create temp file for analysis'''
        temp_file = tempfile.NamedTemporaryFile(prefix=filename.split('.'[0])[0], suffix='.' + filename.split('.'[0])[1])
        temp_file.write(content)
        self.response_attachments.append(filename)
        
        '''add to cuckoo tasks'''
        task_id = self.db.add_path(temp_file.name, timeout=10, package=filename.split('.'[0])[1])
        temp_file.flush()
        if task_id:
            self.taskids.append(task_id)
            self.log_entry.logEvent('[+] File \"%s\" added as task with ID %d' % (filename,task_id))
        else:
            self.taskids.append(task_id)
            self.log_entry.logEvent("[!] Error adding task to database")
            
        '''make sure file gets submitted before we toss it'''
        timeout = time.time() + 120
        while time.time() < timeout:
            if os.path.exists(os.path.join(CUCKOO_ROOT,"storage","analyses",str(task_id),"reports","report.html")): continue
            time.sleep(.25)
        temp_file.close()


    def archive(self, message):

        select_info = self.server.select_folder('INBOX')
        '''cleanup mailbox'''
        self.server.copy(self.message,self.archive_folder)
        self.server.delete_messages(self.message)
        self.server.expunge()
        
    def expunge(self, message):

        select_info = self.server.select_folder('INBOX')
        '''expunge cuckooinbox request'''
        self.server.delete_messages(self.message)
        self.server.expunge()


    def zipResults(self,):
        
        '''create temporary zip file'''
        temp_zip = tempfile.TemporaryFile(prefix='report',suffix='.zip')
        zip_file = zipfile.ZipFile(temp_zip, 'w')
        if self.zip_password: zip_file.setpassword(self.zip_password)
        
        '''set zip to compress'''
        try:
            import zlib
            compression = zipfile.ZIP_DEFLATED
        except:
            compression = zipfile.ZIP_STORED
        modes = { zipfile.ZIP_DEFLATED: 'deflated',
                zipfile.ZIP_STORED:   'stored',}
        
        '''wait for reports to finish then add to list'''
        for id in self.taskids:
            # timeout error handling
            if not os.path.exists(os.path.join(CUCKOO_ROOT,"storage","analyses",str(id),"reports","report.html")):
                self.log_entry.logEvent('cuckooinbox error: report timeout reached on task ID %d.' % id)
            else: 
                zip_file.write(os.path.join(CUCKOO_ROOT,"storage","analyses",str(id),"reports","report.html"),\
                arcname = 'report' + str(id) + '.html', compress_type=compression)
        zip_file.close()
            
        '''attach zip to email message'''
        temp_zip.seek(0)
        email_file = MIMEBase('application', 'zip')
        email_file.set_payload(temp_zip.read())
        Encoders.encode_base64(email_file)
        email_file.add_header('Content-Disposition', 'attachment; filename="report.zip"')
        self.response_msg.attach(email_file)

    def sendReport(self,):

        '''create email header'''
        assert type(self.cc_list)==list
        assert type(self.taskids)==list
        self.response_msg['From'] = self.username
        self.response_msg['To'] = self.sender
        self.response_msg['Cc'] = ", ".join(self.cc_list)
        self.response_msg['Date'] = formatdate(localtime=True)
        self.response_msg['Subject'] = 'cuckooinbox report: ' + self.subject

        '''attach cuckooinbox email body'''
        for id in self.taskids:
	    '''wait for reports to finish before sending'''
	    timeout = time.time() + 120
            while time.time() < timeout:
                if os.path.exists(os.path.join(CUCKOO_ROOT,"storage","analyses",str(id),"reports","report.html")): continue
            	time.sleep(.25)
            if os.path.exists(os.path.join(CUCKOO_ROOT,"storage","analyses",str(id),"reports","inbox.html")):
                file = open(os.path.join(CUCKOO_ROOT,"storage","analyses",str(id),"reports","inbox.html"))
                body = '<html>' + \
                    '<div class="section-title">'+ \
                    '<h2>Task ID %d <small></small></h2>' % id + \
                    '</div>'+ \
                    '<table class="table table-striped table-bordered">'+ \
                    file.read() + \
                    '</html>'
                file.close()
                response_text = ''.join(body)
                self.response_msg.attach(MIMEText(response_text,'html'))
            else: print '[!] Could not find cuckoobox report files.'

        '''wait for analysis to finish and zip the reports'''
        self.zipResults()
        
        '''send the message'''
	if '@gmail.com' in self.username:
	    smtp = smtplib.SMTP('smtp.gmail.com',587)
	    smtp.starttls()
	    smtp.login(self.username, self.passwd)
	else:
            smtp = smtplib.SMTP(self.smtp_server)
            try: smtp.login(self.username,self.passwd)
            except:
                self.log_entry.logEvent('[!] SMTP login failed.')
        try: smtp.sendmail(self.username, self.sender, self.response_msg.as_string())
        except:
            self.log_entry.logEvent('SMTP message %s failed to send.' % self.subject)
        smtp.close()
        self.log_entry.logEvent('[-] Sent "%s" report to %s' % (self.subject, self.sender))
        self.server.logout()
Esempio n. 15
0
class IMAP():
    """
    Central class for IMAP server communication
    """
    Retval = namedtuple('Retval', 'code data')

    def __init__(self, logger, username, password,
                 server='localhost',
                 port=143,
                 starttls=False,
                 imaps=False,
                 tlsverify=True,
                 test=False,
                 timeout=None):
        self.logger = logger
        self.username = username
        self.password = password
        self.server = server
        self.port = port
        self.imaps = imaps
        self.starttls = starttls
        self.timeout = timeout

        self.sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)  # TODO add proto arg
        if tlsverify:
            self.sslcontext.verify_mode = ssl.CERT_REQUIRED
        else:
            self.sslcontext.verify_mode = ssl.CERT_NONE  # TODO improve?

        self.test = test

        self.conn = None

    def do_select_mailbox(func):
        """
        Decorator to do a fresh mailbox SELECT
        """

        def wrapper(*args, **kwargs):
            if len(args) != 1:
                raise AttributeError(
                    'Size of *args tuple "{0}" isn\'t 1. It looks like you haven\'t specified all '
                    'method arguments as named arguments!'.format(
                        args))

            mailbox = None
            for key in ['mailbox', 'source']:
                if key in kwargs.keys():
                    mailbox = kwargs[key]
                    break

            if mailbox is None:
                raise KeyError('Unable to SELECT a mailbox, kwargs "{0}" doesn\'t contain a mailbox name'.format(kwargs))

            result = args[0].select_mailbox(mailbox)
            if not result.code:
                raise RuntimeError(result.data)
            return func(*args, **kwargs)

        return wrapper

    def process_error(self, exception, simple_return=False):
        """
        Process Python exception by logging a message and optionally showing traceback
        """
        trace_info = exc_info()
        err_msg = str(exception)

        if isinstance(exception, IMAPClient.Error):
            err_msg = Helper().byte_to_str(exception.args[0])

        self.logger.error('Catching IMAP exception %s: %s', type(exception), err_msg)

        if self.logger.isEnabledFor(loglevel_DEBUG):
            print_exception(*trace_info)
        del trace_info

        if simple_return:
            return exception
        else:
            return self.Retval(False, err_msg)

    def connect(self, retry=True, logout=False):
        """
        Connect to IMAP server and login
        """
        if self.starttls:
            self.logger.debug('Establishing IMAP connection using STARTTLS/%s to %s and logging in with user %s', self.port, self.server,
                              self.username)
        elif self.imaps:
            self.logger.debug('Establishing IMAP connection using SSL/%s (imaps) to %s and logging in with user %s', self.port, self.server,
                              self.username)

        login = ''
        err_return = None
        try:
            self.conn = IMAPClient(host=self.server,
                                   port=self.port,
                                   use_uid=True,
                                   ssl=self.imaps,
                                   ssl_context=self.sslcontext,
                                   timeout=self.timeout)

            if self.starttls:
                self.conn.starttls(ssl_context=self.sslcontext)

            login = self.conn.login(self.username, self.password)
            login_response = Helper().byte_to_str(login)

            # Test login/auth status
            login_success = False
            noop = self.noop()
            if noop.code and noop.data:
                login_success = True

            if logout:
                return self.disconnect()
            elif login_success:
                return self.Retval(True, login_response)
            else:
                return self.Retval(False, login_response)  # pragma: no cover

        except Exception as e:
            err_return = self.process_error(e)

            if err_return.data == '[AUTHENTICATIONFAILED] Authentication failed.':
                return err_return

            if retry:
                self.logger.error('Trying one more time to login')
                sleep(2)
                return self.connect(retry=False, logout=logout)
            return err_return

    def noop(self):
        """
        Do a noop to test login status
        """
        try:
            noop = self.conn.noop()
            noop_response = Helper().byte_to_str(noop[0])
            noop_resp_pattern_re = regex_compile('^(Success|NOOP completed)')
            login_success = noop_resp_pattern_re.match(noop_response)
            return self.Retval(True, login_success)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def disconnect(self):
        """
        Disconnect from IMAP server
        """
        result = self.conn.logout()
        response = Helper().byte_to_str(result)
        return self.Retval(response == 'Logging out', response)

    def list_mailboxes(self, directory='', pattern='*'):
        """
        Get a listing of folders (mailboxes) on the server
        """
        try:
            raw_list = self.conn.list_folders(directory, pattern)
            nice_list = []

            for mailbox in raw_list:
                flags = []
                for flag in mailbox[0]:
                    flags.append(flag.decode('utf-8'))

                nice_list.append({'name': mailbox[2], 'flags': flags, 'delimiter': mailbox[1].decode("utf-8")})
            return self.Retval(True, nice_list)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def select_mailbox(self, mailbox):
        """
        Select a mailbox to work on
        """
        self.logger.debug('Switching to mailbox %s', mailbox)
        try:
            result = self.conn.select_folder(mailbox)
            response = {}
            for key, value in result.items():
                unicode_key = Helper().byte_to_str(key)
                if unicode_key == 'FLAGS':
                    flags = []
                    for flag in value:
                        flags.append(Helper().byte_to_str(flag))
                    response[unicode_key] = tuple(flags)
                else:
                    response[unicode_key] = value
            return self.Retval(True, response)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def add_mail(self, mailbox, message, flags=(), msg_time=None):
        """
        Add/append a mail to a mailbox
        """
        self.logger.debug('Adding a mail into mailbox %s', mailbox)
        try:
            if not isinstance(message, Mail):
                message = Mail(logger=self.logger, mail_native=message)
            message_native = message.get_native()

            #self.conn.append(mailbox, message, flags, msg_time)
            self._append(mailbox, str(message_native), flags, msg_time)

            # According to rfc4315 we must not return the UID from the response, so we are fetching it ourselves
            uids = self.search_mails(mailbox=mailbox, criteria='HEADER Message-Id "{0}"'.format(message.get_header('Message-Id'))).data[0]

            return self.Retval(True, uids)
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def search_mails(self, mailbox, criteria='ALL', autocreate_mailbox=False):
        """
        Search for mails in a mailbox
        """
        self.logger.debug('Searching for mails in mailbox %s and criteria=\'%s\'', mailbox, criteria)
        try:
            return self.Retval(True, list(self.conn.search(criteria=criteria)))
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def fetch_mails(self, uids, mailbox, return_fields=None):
        """
        Retrieve mails from a mailbox
        """
        self.logger.debug('Fetching mails with uids %s', uids)

        return_raw = True
        if return_fields is None:
            return_raw = False
            return_fields = [b'RFC822']

        mails = {}
        try:
            for uid in uids:
                result = self.conn.fetch(uid, return_fields)

                if not result:
                    continue

                if return_raw:
                    mails[uid] = result[uid]
                else:
                    #mails[uid] = Mail(logger=self.logger, uid=uid, mail_native=email.message_from_bytes(result[uid][b'RFC822']))
                    mails[uid] = Mail(logger=self.logger, mail_native=email.message_from_bytes(result[uid][b'RFC822']))
            return self.Retval(True, mails)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def get_mailflags(self, uids, mailbox):
        """
        Retrieve flags from mails
        """
        try:
            result = self.conn.get_flags(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error('Failed to get flags for mail with uid=%s: %s', uid, result)
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def set_mailflags(self, uids, mailbox, flags=[]):
        """
        Set and retrieve flags from mails
        """
        if self.test:
            self.logger.info('Would have set mail flags on message uids "%s"', str(uids))
            return self.Retval(True, None)
        else:
            self.logger.debug('Setting flags=%s on mails uid=%s', flags, uids)
            try:
                result = self.conn.set_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error('Failed to set and get flags for mail with uid=%s: %s', uid, result)
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def add_mailflags(self, uids, mailbox, flags=[]):
        """
        Add and retrieve flags from mails
        """
        if self.test:
            self.logger.info('Would have added mail flags on message uids "%s"', str(uids))
            return self.Retval(True, None)
        else:
            self.logger.debug('Adding flags=%s on mails uid=%s', flags, uids)
            try:
                result = self.conn.add_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error('Failed to add and get flags for mail with uid=%s: %s', uid, result)
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def move_mail(self, message_ids, source, destination, delete_old=True, expunge=True, add_flags=None, set_flags=None):
        """
        Move a mail from a mailbox to another
        """
        return self.copy_mails(message_ids=message_ids,
                               source=source,
                               destination=destination,
                               delete_old=delete_old,
                               expunge=expunge,
                               add_flags=add_flags,
                               set_flags=set_flags)

    @do_select_mailbox
    def copy_mails(self, source, destination, message_ids=None, delete_old=False, expunge=False, add_flags=None, set_flags=None):
        """
        Copies one or more mails from a mailbox into another
        """
        if self.test:
            if delete_old:
                self.logger.info('Would have moved mail Message-Ids="%s" from "%s" to "%s", skipping because of beeing in testmode',
                                 message_ids, source, destination)
            else:
                self.logger.info('Would have copied mails with Message-Ids="%s" from "%s" to "%s", skipping because of beeing in testmode',
                                 message_ids, source, destination)
            return self.Retval(True, None)
        else:
            try:
                if delete_old:
                    self.logger.debug('Moving mail Message-Ids="%s" from "%s" to "%s"', message_ids, source, destination)
                else:
                    self.logger.debug('Copying mail Message-Ids="%s" from "%s" to "%s"', message_ids, source, destination)

                #if message_ids is None:
                #    message_ids = []
                #    result = self.fetch_mails(uids=uids, mailbox=source)
                #    if not result.code:
                #        self.logger.error('Failed to determine Message-Id by uids for mail with uids "%s"', uids)
                #        return result
                #    message_ids.append(result.data.keys())

                if not self.mailbox_exists(destination).data:
                    self.logger.info('Destination mailbox %s doesn\'t exist, creating it for you', destination)

                    result = self.create_mailbox(mailbox=destination)
                    if not result.code:
                        self.logger.error('Failed to create the mailbox %s: %s', source, result.data)  # pragma: no cover
                        return result  # pragma: no cover

                uids = []
                for message_id in message_ids:
                    result = self.search_mails(mailbox=source, criteria='HEADER Message-Id "{0}"'.format(message_id))

                    if not result.code or len(result.data) == 0:
                        self.logger.error('Failed to determine uid by Message-Id for mail with Message-Id "%s"', message_id)
                        return self.Retval(False, result.data)
                    uids.append(result.data[0])

                result = self.select_mailbox(source)
                if not result.code:
                    return result  # pragma: no cover

                self.conn.copy(uids, destination)

                if delete_old:
                    result = self.delete_mails(uids=uids, mailbox=source)
                    if not result.code:
                        self.logger.error('Failed to remove old mail with Message-Id="%s"/uids="%s": %s', message_ids, uids,
                                          result.data)  # pragma: no cover
                        return result  # pragma: no cover

                    if expunge:  # TODO don't expunge by default
                        result = self.expunge(mailbox=source)
                        if not result.code:
                            self.logger.error('Failed to expunge on mailbox %s: %s', source, result.data)  # pragma: no cover
                            return result  # pragma: no cover

                dest_uids = []
                for message_id in message_ids:
                    result = self.search_mails(mailbox=destination, criteria='HEADER Message-Id "{0}"'.format(message_id))
                    if not result.code:
                        self.logger.error('Failed to determine uid by Message-Id for mail with Message-Id "%s"',
                                          message_id)  # pragma: no cover
                        return result  # pragma: no cover
                    dest_uids.append(result.data[0])

                if isinstance(set_flags, list):
                    self.set_mailflags(uids=dest_uids, mailbox=destination, flags=set_flags)
                if add_flags:
                    self.add_mailflags(uids=dest_uids, mailbox=destination, flags=add_flags)

                return self.Retval(True, dest_uids)

            except IMAPClient.Error as e:
                return self.process_error(e)

    def _append(self, folder, msg, flags=(), msg_time=None):  # TODO
        """
        FORKED FORM IMAPCLIENT
        """
        if msg_time:
            if not msg_time.tzinfo:  # pragma: no cover
                msg_time = msg_time.replace(tzinfo=FixedOffset.for_system())  # pragma: no cover

            time_val = '"{0}"'.format(msg_time.strftime("%d-%b-%Y %H:%M:%S %z"))
            time_val = imapclient.imapclient.to_unicode(time_val)
        else:
            time_val = None

        return self.conn._command_and_check('append', self.conn._normalise_folder(folder), imapclient.imapclient.seq_to_parenstr(flags),
                                            time_val, Helper.str_to_bytes(msg),
                                            unpack=True)

    @do_select_mailbox
    def expunge(self, mailbox):
        """
        Expunge mails form a mailbox
        """
        self.logger.debug('Expunge mails from mailbox %s', mailbox)
        try:
            return self.Retval(True, b'Expunge completed.' in self.conn.expunge())
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    def create_mailbox(self, mailbox):
        """
        Create a mailbox
        """
        self.logger.debug('Creating mailbox %s', mailbox)
        try:
            return self.Retval(True, self.conn.create_folder(mailbox) == b'Create completed.')
        except IMAPClient.Error as e:
            return self.process_error(e)

    def mailbox_exists(self, mailbox):
        """
        Check whether a mailbox exists
        """
        try:
            return self.Retval(True, self.conn.folder_exists(mailbox))
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    @do_select_mailbox
    def delete_mails(self, uids, mailbox):
        """
        Delete mails
        """
        self.logger.debug('Deleting mails with uid="%s"', uids)
        try:
            result = self.conn.delete_messages(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error('Failed to get flags for mail with uid=%s after deleting it: %s', uid, result)
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)
        except IMAPClient.Error as e:
            return self.process_error(e)
Esempio n. 16
0
class IMAP():
    """
    Central class for IMAP server communication
    """
    Retval = namedtuple('Retval', 'code data')

    def __init__(self,
                 logger,
                 username,
                 password,
                 server='localhost',
                 port=143,
                 starttls=False,
                 imaps=False,
                 tlsverify=True,
                 test=False,
                 timeout=None):
        self.logger = logger
        self.username = username
        self.password = password
        self.server = server
        self.port = port
        self.imaps = imaps
        self.starttls = starttls
        self.timeout = timeout

        self.sslcontext = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
        if tlsverify:
            self.sslcontext.verify_mode = ssl.CERT_REQUIRED
        else:
            self.sslcontext.check_hostname = False
            self.sslcontext.verify_mode = ssl.CERT_NONE

        self.test = test
        self.conn = None

    def do_select_mailbox(func):
        """
        Decorator to do a fresh mailbox SELECT
        """
        def wrapper(*args, **kwargs):
            if len(args) != 1:
                raise AttributeError(
                    'Size of *args tuple "{0}" isn\'t 1. It looks like you haven\'t specified all '
                    'method arguments as named arguments!'.format(args))

            mailbox = None
            for key in ['mailbox', 'source']:
                if key in kwargs.keys():
                    mailbox = kwargs[key]
                    break

            if mailbox is None:
                raise KeyError(
                    'Unable to SELECT a mailbox, kwargs "{0}" doesn\'t contain a mailbox name'
                    .format(kwargs))

            result = args[0].select_mailbox(mailbox)
            if not result.code:
                raise RuntimeError(result.data)
            return func(*args, **kwargs)

        return wrapper

    def process_error(self, exception, simple_return=False):
        """
        Process Python exception by logging a message and optionally showing traceback
        """
        trace_info = exc_info()
        err_msg = str(exception)

        if isinstance(exception, IMAPClient.Error):
            err_msg = Helper().byte_to_str(exception.args[0])

        self.logger.error("Catching IMAP exception {}: {}".format(
            type(exception), err_msg))

        if self.logger.isEnabledFor(loglevel_DEBUG):
            print_exception(*trace_info)
        del trace_info

        if simple_return:
            return exception
        else:
            return self.Retval(False, err_msg)

    def connect(self, retry=True, logout=False):
        """
        Connect to IMAP server and login
        """
        if self.starttls:
            self.logger.debug(
                'Establishing IMAP connection using STARTTLS/{} to {} and logging in with user {}'
                .format(self.port, self.server, self.username))
        elif self.imaps:
            self.logger.debug(
                'Establishing IMAP connection using SSL/{} (imaps) to {} and logging in with user {}'
                .format(self.port, self.server, self.username))
        try:
            self.conn = IMAPClient(host=self.server,
                                   port=self.port,
                                   use_uid=True,
                                   ssl=self.imaps,
                                   ssl_context=self.sslcontext,
                                   timeout=self.timeout)

            if self.starttls:
                self.conn.starttls(ssl_context=self.sslcontext)

            login = self.conn.login(self.username, self.password)
            login_response = Helper().byte_to_str(login)

            # Test login/auth status
            login_success = False
            noop = self.noop()
            if noop.code and noop.data:
                login_success = True

            if logout:
                return self.disconnect()
            elif login_success:
                return self.Retval(True, login_response)
            else:
                return self.Retval(False, login_response)  # pragma: no cover

        except exceptions.LoginError as e:
            return self.process_error(e)

        except Exception as e:
            err_return = self.process_error(e)

            if retry:
                self.logger.error('Trying one more time to login')
                sleep(2)
                return self.connect(retry=False, logout=logout)
            return err_return

    def noop(self):
        """
        Do a noop to test login status
        """
        try:
            noop = self.conn.noop()
            noop_response = Helper().byte_to_str(noop[0])
            noop_resp_pattern_re = regex_compile('^(Success|NOOP completed)')
            login_success = noop_resp_pattern_re.match(noop_response)
            return self.Retval(True, login_success)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def disconnect(self):
        """
        Disconnect from IMAP server
        """
        result = self.conn.logout()
        response = Helper().byte_to_str(result)
        return self.Retval(response == 'Logging out', response)

    def list_mailboxes(self, directory='', pattern='*'):
        """
        Get a listing of folders (mailboxes) on the server
        """
        try:
            raw_list = self.conn.list_folders(directory, pattern)
            nice_list = []

            for mailbox in raw_list:
                flags = []
                for flag in mailbox[0]:
                    flags.append(flag.decode('utf-8'))

                nice_list.append({
                    'name': mailbox[2],
                    'flags': flags,
                    'delimiter': mailbox[1].decode("utf-8")
                })
            return self.Retval(True, nice_list)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def select_mailbox(self, mailbox):
        """
        Select a mailbox to work on
        """
        self.logger.debug('Switching to mailbox {}'.format(mailbox))
        try:
            result = self.conn.select_folder(mailbox)
            response = {}
            for key, value in result.items():
                unicode_key = Helper().byte_to_str(key)
                if unicode_key == 'FLAGS':
                    flags = []
                    for flag in value:
                        flags.append(Helper().byte_to_str(flag))
                    response[unicode_key] = tuple(flags)
                else:
                    response[unicode_key] = value
            return self.Retval(True, response)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def add_mail(self, mailbox, message, flags=(), msg_time=None):
        """
        Add/append a mail to a mailbox
        """
        self.logger.debug('Adding a mail into mailbox {}'.format(mailbox))
        try:
            if not isinstance(message, Mail):
                message = Mail(logger=self.logger, mail_native=message)

            self.conn.append(mailbox, str(message.get_native()), flags,
                             msg_time)

            # According to rfc4315 we must not return the UID from the response, so we are fetching it ourselves
            uids = self.search_mails(mailbox=mailbox,
                                     criteria='HEADER Message-Id "{}"'.format(
                                         message.get_message_id())).data[0]

            return self.Retval(True, uids)
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def search_mails(self, mailbox, criteria='ALL', autocreate_mailbox=False):
        """
        Search for mails in a mailbox
        """
        self.logger.debug(
            'Searching for mails in mailbox {} and criteria=\'{}\''.format(
                mailbox, criteria))
        try:
            return self.Retval(True, list(self.conn.search(criteria=criteria)))
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def fetch_mails(self, uids, mailbox, return_fields=None):
        """
        Retrieve mails from a mailbox
        """
        self.logger.debug('Fetching mails with uids {}'.format(uids))

        return_raw = True
        if return_fields is None:
            return_raw = False
            return_fields = [b'RFC822']

        mails = {}
        try:
            for uid in uids:
                result = self.conn.fetch(uid, return_fields)

                if not result:
                    continue

                if return_raw:
                    mails[uid] = result[uid]
                else:
                    # mails[uid] = Mail(logger=self.logger, uid=uid, mail_native=email.message_from_bytes(result[uid][b'RFC822']))
                    mails[uid] = Mail(logger=self.logger,
                                      mail_native=email.message_from_bytes(
                                          result[uid][b'RFC822']))
            return self.Retval(True, mails)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def get_mailflags(self, uids, mailbox):
        """
        Retrieve flags from mails
        """
        try:
            result = self.conn.get_flags(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error(
                        'Failed to get flags for mail with uid={}: {}'.format(
                            uid, result))
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def set_mailflags(self, uids, mailbox, flags=[]):
        """
        Set and retrieve flags from mails
        """
        if self.test:
            self.logger.info(
                'Would have set mail flags on message uids "{}"'.format(
                    str(uids)))
            return self.Retval(True, None)
        else:
            self.logger.debug('Setting flags={} on mails uid={}', flags, uids)
            try:
                result = self.conn.set_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error(
                            'Failed to set and get flags for mail with uid={}: {}'
                            .format(uid, result))
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def add_mailflags(self, uids, mailbox, flags=[]):
        """
        Add and retrieve flags from mails
        """
        if self.test:
            self.logger.info(
                'Would have added mail flags on message uids "{}"'.format(
                    str(uids)))
            return self.Retval(True, None)
        else:
            self.logger.debug('Adding flags={} on mails uid={}', flags, uids)
            try:
                result = self.conn.add_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error(
                            'Failed to add and get flags for mail with uid={}: {}'
                            .format(uid, result))
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def move_mail(self,
                  message_ids,
                  source,
                  destination,
                  delete_old=True,
                  expunge=True,
                  add_flags=None,
                  set_flags=None):
        """
        Move a mail from a mailbox to another
        """
        return self.copy_mails(message_ids=message_ids,
                               source=source,
                               destination=destination,
                               delete_old=delete_old,
                               expunge=expunge,
                               add_flags=add_flags,
                               set_flags=set_flags)

    @do_select_mailbox
    def copy_mails(self,
                   source,
                   destination,
                   message_ids=None,
                   delete_old=False,
                   expunge=False,
                   add_flags=None,
                   set_flags=None):
        """
        Copies one or more mails from a mailbox into another
        """
        if self.test:
            if delete_old:
                self.logger.info(
                    'Would have moved mail Message-Ids="{}" from "{}" to "{}", skipping because of beeing in testmode'
                    .format(message_ids, source, destination))
            else:
                self.logger.info(
                    'Would have copied mails with Message-Ids="{}" from "{}" to "{}", skipping because of beeing in testmode'
                    .format(message_ids, source, destination))
            return self.Retval(True, None)
        else:
            try:
                if delete_old:
                    self.logger.debug(
                        'Moving mail Message-Ids="{}" from "{}" to "{}"'.
                        format(message_ids, source, destination))
                else:
                    self.logger.debug(
                        'Copying mail Message-Ids="{}" from "{}" to "{}"'.
                        format(message_ids, source, destination))

                # if message_ids is None:
                #    message_ids = []
                #    result = self.fetch_mails(uids=uids, mailbox=source)
                #    if not result.code:
                #        self.logger.error('Failed to determine Message-Id by uids for mail with uids "{}"', uids)
                #        return result
                #    message_ids.append(result.data.keys())

                if not self.mailbox_exists(destination).data:
                    self.logger.info(
                        'Destination mailbox {} doesn\'t exist, creating it for you'
                        .format(destination))

                    result = self.create_mailbox(mailbox=destination)
                    if not result.code:
                        self.logger.error(
                            'Failed to create the mailbox {}: {}'.format(
                                source, result.data))  # pragma: no cover
                        return result  # pragma: no cover

                uids = []
                for message_id in message_ids:
                    result = self.search_mails(
                        mailbox=source,
                        criteria='HEADER Message-Id "{}"'.format(message_id))

                    if not result.code or len(result.data) == 0:
                        self.logger.error(
                            'Failed to determine uid by Message-Id for mail with Message-Id "{}"'
                            .format(message_id))
                        return self.Retval(False, result.data)
                    uids.append(result.data[0])

                result = self.select_mailbox(source)
                if not result.code:
                    return result  # pragma: no cover

                self.conn.copy(uids, destination)

                if delete_old:
                    result = self.delete_mails(uids=uids, mailbox=source)
                    if not result.code:
                        self.logger.error(
                            'Failed to remove old mail with Message-Id="{}"/uids="{}": {}'
                            .format(message_ids, uids,
                                    result.data))  # pragma: no cover
                        return result  # pragma: no cover

                    if expunge:  # TODO don't expunge by default
                        result = self.expunge(mailbox=source)
                        if not result.code:
                            self.logger.error(
                                'Failed to expunge on mailbox {}: {}'.format(
                                    source, result.data))  # pragma: no cover
                            return result  # pragma: no cover

                dest_uids = []
                for message_id in message_ids:
                    result = self.search_mails(
                        mailbox=destination,
                        criteria='HEADER Message-Id "{}"'.format(message_id))
                    if not result.code:
                        self.logger.error(
                            'Failed to determine uid by Message-Id for mail with Message-Id "{}"'
                            .format(message_id))  # pragma: no cover
                        return result  # pragma: no cover
                    dest_uids.append(result.data[0])

                if isinstance(set_flags, list):
                    self.set_mailflags(uids=dest_uids,
                                       mailbox=destination,
                                       flags=set_flags)
                if add_flags:
                    self.add_mailflags(uids=dest_uids,
                                       mailbox=destination,
                                       flags=add_flags)

                return self.Retval(True, dest_uids)

            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def expunge(self, mailbox):
        """
        Expunge mails form a mailbox
        """
        self.logger.debug('Expunge mails from mailbox {}'.format(mailbox))
        try:
            return self.Retval(True, b'Expunge completed.'
                               in self.conn.expunge())
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    def create_mailbox(self, mailbox):
        """
        Create a mailbox
        """
        self.logger.debug('Creating mailbox {}'.format(mailbox))
        try:
            return self.Retval(
                True,
                self.conn.create_folder(mailbox) == b'Create completed.')
        except IMAPClient.Error as e:
            return self.process_error(e)

    def mailbox_exists(self, mailbox):
        """
        Check whether a mailbox exists
        """
        try:
            return self.Retval(True, self.conn.folder_exists(mailbox))
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    @do_select_mailbox
    def delete_mails(self, uids, mailbox):
        """
        Delete mails
        """
        self.logger.debug('Deleting mails with uid="{}"'.format(uids))
        try:
            result = self.conn.delete_messages(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error(
                        'Failed to get flags for mail with uid={} after deleting it: {}'
                        .format(uid, result))
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)
        except IMAPClient.Error as e:
            return self.process_error(e)
Esempio n. 17
0
class SourceDriverMail(SourceDriver):
    def __init__(self, instanceName: str, settings: Settings,
                 parser: MessageParser) -> None:
        super().__init__("mail", instanceName, settings, parser)
        # Settings
        self.__server = self.getSettingString("server", "")
        self.__user = self.getSettingString("user", "")
        self.__password = self.getSettingString("password", "")
        self.__ssl = self.getSettingBoolean("ssl", True)
        self.__fix_weak_dh = self.getSettingBoolean("fix_weak_dh", False)
        self.__allowlist = self.getSettingList("allowlist", [])
        self.__denylist = self.getSettingList("denylist", [])
        self.__cleanup = self.getSettingString("cleanup", "")
        self.__archive_folder = self.getSettingString("archive_folder",
                                                      "Archive")

        if self.__cleanup == "Delete":
            self.print("Cleanup strategy is delete")
        elif self.__cleanup == "Archive":
            self.print("Cleanup strategy is archive")
        elif self.__cleanup == "":
            self.print("Cleanup is disabled")
        else:
            self.fatal("Unknown cleanup strategy")

        # Internal
        self.__healthy = False

        self.__connect()

    def retrieveEvent(self) -> Optional[SourceEvent]:
        try:
            if self.isDebug():
                self.print("Checking for new mails")
            messages = self.__imap_client.search('UNSEEN')
            for uid, message_data in self.__imap_client.fetch(
                    messages, "RFC822").items():
                message = email.message_from_bytes(message_data[b"RFC822"],
                                                   policy=policy.default)
                sender = parseaddr(message.get("From"))[1]
                sourceEvent = SourceEvent()
                sourceEvent.source = SourceEvent.SOURCE_MAIL
                sourceEvent.timestamp = datetime.datetime.strptime(
                    message.get('Date'), "%a, %d %b %Y %H:%M:%S %z").strftime(
                        SourceEvent.TIMESTAMP_FORMAT)
                sourceEvent.sender = sender
                sourceEvent.raw = message.get_body(('plain', )).get_content()
                if self.isSenderAllowed(allowlist=self.__allowlist,
                                        denylist=self.__denylist,
                                        sender=sender):
                    parsedSourceEvent = self.parser.parseMessage(
                        sourceEvent, None)  # type: ignore[union-attr]
                    self.__do_cleanup(uid)
                    return parsedSourceEvent
                else:
                    self.error("Received unhandled message (ignored sender)")
                    return UnhandledEvent.fromSourceEvent(
                        sourceEvent, UnhandledEvent.CAUSE_IGNORED_SENDER)
        except (timeout, OSError) as e:
            self.error("Connection to mailserver timed out")
            self.__healthy = False
            self.__connect()

    def getSourceState(self) -> SourceState:
        if self.__healthy:
            return SourceState.OK
        return SourceState.ERROR

    def __connect(self):
        if self.__fix_weak_dh:
            context = ssl.SSLContext(
                ssl.PROTOCOL_TLSv1_2)  # Workaround for weak dh key
            context.set_ciphers('DEFAULT@SECLEVEL=1')
        else:
            context = ssl.SSLContext()

        try:
            if self.isDebug():
                self.print("Connecting to server {}".format(self.__server))
            self.__imap_client = IMAPClient(self.__server,
                                            use_uid=True,
                                            ssl=self.__ssl,
                                            ssl_context=context,
                                            timeout=1.0)
        except gaierror:
            self.error("Failed to connect to Mail Server")
        except ssl.SSLError:
            self.fatal("Failed to connect to Mail Server (TLS Error)")
        else:
            try:
                if self.isDebug():
                    self.print("Login as user {}".format(self.__user))
                self.__imap_client.login(self.__user, self.__password)
            except LoginError:
                self.error("Mail Server login failed")
            else:
                if self.isDebug():
                    self.print("Login successful")
                self.__healthy = True
                self.__imap_client.select_folder('INBOX', readonly=False)

    def __create_imap_folder(self, folder):
        if not self.__imap_client.folder_exists(folder):
            self.print("Folder {} does not exist creating")
            self.__imap_client.create_folder(folder)

    def __do_cleanup(self, uid):
        if self.__cleanup == "Archive":
            self.__create_imap_folder(self.__archive_folder)
            self.__imap_client.copy(uid, self.__archive_folder)
        if self.__cleanup == "Delete" or self.__cleanup == "Archive":
            self.__imap_client.delete_messages(uid)
            self.__imap_client.expunge(uid)
        digesters = []

        if os.path.isfile("my_digesters_setup.py"):
            from my_digesters_setup import add_digesters
        else:
            # Copy my_digesters_setup_template.py to the my_digesters_setup.py,
            # if you're wanting to customize the digesters.
            from my_digesters_setup_sample import add_digesters

        # Get Digesters from my_digesters_setup.py
        add_digesters(digesters)

        DigestionProcessor(notification_folder, digest_folder, digesters, options.print_summary,
                           options.sender_to_implicate, options.move_unmatched, options.digest_folder_name)\
            .doit()

    try:
        digest_folder.expunge()
    except IMAPClient.AbortError, e:
        print "Error expunging digest folder"
    digest_folder.logout()

    try:
        notification_folder.expunge()
    except IMAPClient.AbortError, e:
        print "Error expunging notification folder"
    notification_folder.logout()

    if command is "BASH-OPERATIONS":
        sys.exit(202)  ## HTTP 'accepted' (FYI)
Esempio n. 19
0
 def remove_message(client: IMAPClient):
     client.select_folder(EmailFolders.URGENT)
     client.delete_messages(next_uid_urgent)
     client.expunge()
Esempio n. 20
0
class DirectImap:
    def __init__(self, account):
        self.account = account
        self.logid = account.get_config("displayname") or id(account)
        self._idling = False
        self.connect()

    def connect(self):
        host = self.account.get_config("configured_mail_server")
        port = int(self.account.get_config("configured_mail_port"))
        security = int(self.account.get_config("configured_mail_security"))

        user = self.account.get_config("addr")
        pw = self.account.get_config("mail_pw")

        if security == const.DC_SOCKET_PLAIN:
            ssl_context = None
        else:
            ssl_context = ssl.create_default_context()

            # don't check if certificate hostname doesn't match target hostname
            ssl_context.check_hostname = False

            # don't check if the certificate is trusted by a certificate authority
            ssl_context.verify_mode = ssl.CERT_NONE

        if security == const.DC_SOCKET_STARTTLS:
            self.conn = IMAPClient(host, port, ssl=False)
            self.conn.starttls(ssl_context)
        elif security == const.DC_SOCKET_PLAIN:
            self.conn = IMAPClient(host, port, ssl=False)
        elif security == const.DC_SOCKET_SSL:
            self.conn = IMAPClient(host, port, ssl_context=ssl_context)
        self.conn.login(user, pw)

        self.select_folder("INBOX")

    def shutdown(self):
        try:
            self.conn.idle_done()
        except (OSError, IMAPClientError):
            pass
        try:
            self.conn.logout()
        except (OSError, IMAPClientError):
            print("Could not logout direct_imap conn")

    def select_folder(self, foldername):
        assert not self._idling
        return self.conn.select_folder(foldername)

    def select_config_folder(self, config_name):
        """ Return info about selected folder if it is
        configured, otherwise None. """
        if "_" not in config_name:
            config_name = "configured_{}_folder".format(config_name)
        foldername = self.account.get_config(config_name)
        if foldername:
            return self.select_folder(foldername)

    def list_folders(self):
        """ return list of all existing folder names"""
        assert not self._idling
        folders = []
        for meta, sep, foldername in self.conn.list_folders():
            folders.append(foldername)
        return folders

    def delete(self, range, expunge=True):
        """ delete a range of messages (imap-syntax).
        If expunge is true, perform the expunge-operation
        to make sure the messages are really gone and not
        just flagged as deleted.
        """
        self.conn.set_flags(range, [DELETED])
        if expunge:
            self.conn.expunge()

    def get_all_messages(self):
        assert not self._idling

        # Flush unsolicited responses. IMAPClient has problems
        # dealing with them: https://github.com/mjs/imapclient/issues/334
        # When this NOOP was introduced, next FETCH returned empty
        # result instead of a single message, even though IMAP server
        # can only return more untagged responses than required, not
        # less.
        self.conn.noop()

        return self.conn.fetch(ALL, [FLAGS])

    def get_unread_messages(self):
        assert not self._idling
        res = self.conn.fetch(ALL, [FLAGS])
        return [uid for uid in res if SEEN not in res[uid][FLAGS]]

    def mark_all_read(self):
        messages = self.get_unread_messages()
        if messages:
            res = self.conn.set_flags(messages, [SEEN])
            print("marked seen:", messages, res)

    def get_unread_cnt(self):
        return len(self.get_unread_messages())

    def dump_account_info(self, logfile):
        def log(*args, **kwargs):
            kwargs["file"] = logfile
            print(*args, **kwargs)

        cursor = 0
        for name, val in self.account.get_info().items():
            entry = "{}={}".format(name.upper(), val)
            if cursor + len(entry) > 80:
                log("")
                cursor = 0
            log(entry, end=" ")
            cursor += len(entry) + 1
        log("")

    def dump_imap_structures(self, dir, logfile):
        assert not self._idling
        stream = io.StringIO()

        def log(*args, **kwargs):
            kwargs["file"] = stream
            print(*args, **kwargs)

        empty_folders = []
        for imapfolder in self.list_folders():
            self.select_folder(imapfolder)
            messages = list(self.get_all_messages())
            if not messages:
                empty_folders.append(imapfolder)
                continue

            log("---------", imapfolder, len(messages), "messages ---------")
            # get message content without auto-marking it as seen
            # fetching 'RFC822' would mark it as seen.
            requested = [b'BODY.PEEK[]', FLAGS]
            for uid, data in self.conn.fetch(messages, requested).items():
                body_bytes = data[b'BODY[]']
                if not body_bytes:
                    log("Message", uid, "has empty body")
                    continue

                flags = data[FLAGS]
                path = pathlib.Path(str(dir)).joinpath("IMAP", self.logid,
                                                       imapfolder)
                path.mkdir(parents=True, exist_ok=True)
                fn = path.joinpath(str(uid))
                fn.write_bytes(body_bytes)
                log("Message", uid, fn)
                email_message = email.message_from_bytes(body_bytes)
                log("Message", uid, flags, "Message-Id:",
                    email_message.get("Message-Id"))

        if empty_folders:
            log("--------- EMPTY FOLDERS:", empty_folders)

        print(stream.getvalue(), file=logfile)

    def idle_start(self):
        """ switch this connection to idle mode. non-blocking. """
        assert not self._idling
        res = self.conn.idle()
        self._idling = True
        return res

    def idle_check(self, terminate=False):
        """ (blocking) wait for next idle message from server. """
        assert self._idling
        self.account.log("imap-direct: calling idle_check")
        res = self.conn.idle_check(timeout=30)
        if len(res) == 0:
            raise TimeoutError
        if terminate:
            self.idle_done()
        self.account.log("imap-direct: idle_check returned {!r}".format(res))
        return res

    def idle_wait_for_seen(self):
        """ Return first message with SEEN flag
        from a running idle-stream REtiurn.
        """
        while 1:
            for item in self.idle_check():
                if item[1] == FETCH:
                    if item[2][0] == FLAGS:
                        if SEEN in item[2][1]:
                            return item[0]

    def idle_done(self):
        """ send idle-done to server if we are currently in idle mode. """
        if self._idling:
            res = self.conn.idle_done()
            self._idling = False
            return res
Esempio n. 21
0
class EmailOperation(object):
    def __init__(self, user_name, pwd):
        print("使用imapclient,操作邮件...")
        self.server = IMAPClient('imap.139.com', use_uid=True, ssl=False)
        self.username = user_name
        self.password = pwd

    def logout(self):
        self.server.logout()

    # 删除邮件
    def _del(self, messages):
        #         print('del message')
        self.server.delete_messages(messages)
        self.server.expunge()

    def _mv(self, message, folder):
        self.server.copy(message, folder)
        time.sleep(1)
        self._del(message)

    def _into(self, box):
        # 'INBOX','草稿箱','已发送','已删除','100'
        self.server.select_folder(box)

    def _get_uids(self):
        # 获取序列
        uids = self.server.search(['NOT', 'DELETED'])
        return uids

    def email_body(self, mss):
        # 获取某序列id的原始信息
        raw_messages = self.server.fetch([mss], ['BODY[]', 'FLAGS'])
        #         pprint.pprint(rawMessages)
        # 使用pyzmail,返回主体信息
        message = pyzmail.PyzMessage.factory(raw_messages[mss][b'BODY[]'])

        # 如果信息为空,返回None
        if not message:
            return None
        # 获取正文内容
        if message.text_part != None:
            lines = message.text_part.get_payload().decode(
                message.text_part.charset)

        str = ''
        # 去除内容中的回车
        for line in lines:
            if line not in ['\n', '\r']:
                str += line

        print(str)
        # body自动
        body = {
            'subject': message.get_subject(),
            'from': message.get_address('from'),
            'to': message.get_address('to'),
            'mainbody': str
        }
        return body

    def del_all_message(self, folder):
        '''清空某个文件夹下的所有邮件'''
        self._into(folder)

        uids = self._get_uids()
        if len(uids) == 0:
            return
        else:
            self._del(uids)

    def del_new_message(self, fforlder):
        '''把最近一封新邮件已到目的目录'''
        self._into(fforlder)

        uids = self._get_uids()

        if len(uids) == 0:
            return
        else:
            self._del(uids[-1])

    def move_all_message_to_folder(self, fforlder, tforlder):
        '''把maessage/messages 从 ffolder 移动到 tforlder'''

        self._into(fforlder)

        uids = self._get_uids()
        # 邮件数量为空
        if len(uids) == 0:
            print("%s 数量为:0,该操作无效" % fforlder)
            return True
        self._mv(uids, tforlder)

    def check_new_message(self):
        '''判断最新一封邮件,是否包含某个字段,显示邮件数量'''
        self._into("INBOX")

        uids = self._get_uids()

        print('current INBOX email: %s' % str(len(uids)))

        if len(uids) in [0, 1]:
            print("不执行,目前邮件数量为:%r" % len(uids))
            return

        if self.email_body(uids[-1])['subject'] == 'testReceive':
            self._del(uids[-1])

    def delete_newest_mail(self):
        '''删除最新的一封邮件'''
        try:
            is_true = False
            self.server.login(self.username, self.password)
            self.del_new_message('INBOX')
            is_true = True
        except BaseException as error:
            print(error)
            print("删除邮件可能出现错误")
        finally:
            self.logout()
            return is_true

    def clear_forlder(self, l=[]):
        '''清空邮箱某个文件夹'''
        '''
        sample:
        clearForlder(['100', 'INBOX'])
        '''
        is_true = False
        if len(l) == 0:
            return is_true
        try:
            self.server.login(self.username, self.password)
            for f in l:
                print("clear Forlder: %s" % f)
                self.del_all_message(f)
                time.sleep(1)

            is_true = True
        except BaseException as error:
            print(error)
            print("删除邮件可能出现错误")
        finally:
            self.logout()
            return is_true

    def move_forlder(self, l=[]):
        '''移动邮件
        sample:
            moveForlder(['100', 'INBOX'])
        '''
        is_true = False
        if len(l) == 0:
            return is_true
        try:
            self.server.login(self.username, self.password)
            self.move_all_message_to_folder(l[0], l[1])
            print("移动邮件成功:%s => %s" % (l[0], l[1]))
            is_true = True
        except BaseException as error:
            print(error)
            print("清空邮箱某个文件夹可能出现错误")
        finally:
            self.logout()
            return is_true

    def check_inbox_cnt(self):
        '''获取邮件数量'''
        try:
            is_true = 0
            self.server.login(self.username, self.password)

            self._into("INBOX")
            uids = self._get_uids()
            # 数量为 0
            if len(uids) == 0:
                return 0
            # 判断
            if len(uids) == 100:
                print("100封邮件")
                return 0
            elif len(uids) < 100:
                print('邮件数量少于100封')
                return 0
            else:
                cnt = len(uids) - 100
                print('需要删除邮件数量为:%d' % cnt)
                is_true = cnt
        except BaseException as error:
            print(error)
            print("删除邮件可能出现错误")
        finally:
            self.logout()
            return is_true

    def check_inbox(self):
        '''确保收件箱有100封邮件'''
        try:
            is_true = True
            self.server.login(self.username, self.password)

            self._into("INBOX")
            uids = self._get_uids()
            all = len(uids)
            # 数量为 0
            if all == 0:
                return False
            # 判断
            if all == 100:
                print("100封邮件")
                return is_true
            elif all < 100:
                print('邮件数量少于100封')
                return False
            else:
                print('需要删除邮件数量为:%d' % (all - 100))
                #                 print(Uids[100:])
                self._del(uids[100:])
                return is_true
        except BaseException as error:
            print(error)
            print("删除邮件可能出现错误")
            is_true = False
        finally:
            self.logout()
            return is_true

    def seen(self):
        '''将收件箱邮件,标记已读'''
        self.server.login(self.username, self.password)
        self._into("INBOX")
        # 搜索 未读邮件
        typ = self.server.search([u'UNSEEN'])
        # 把邮件改为已读
        for num in typ:
            print(num)
            self.server.set_flags(num, [u'Seen'])
    )
    originalFromName = data[b"ENVELOPE"].from_[0].name
    if (
        originalFromName is None
    ):  # from_ is a tuple of Address objects that represent one or more addresses from the “From” header, or None if header does not exist.
        originalFromName = data[b"ENVELOPE"].from_[0].mailbox.decode("utf-8")
    else:
        originalFromName = originalFromName.decode("utf-8")

    encodedText = data[b"BODY[TEXT]"].decode()

    if fromAddress in MAILING_LIST_USERS or fromAddress in TOLERATED_ALTERNATE_ADDRESSES:
        sendToAllUsers(data[b"ENVELOPE"].subject.decode("utf-8"), encodedText, fromAddress, originalFromName)
    else:
        sendToAdmin(data[b"ENVELOPE"].subject, encodedText, fromAddress)
        logging.warning(
            "{} tried to sent to the list but is not authorized. Forwarded to admin instead".format(fromAddress)
        )
    allNewIds.append(msgid)  # save for later deletion

if len(allNewIds):
    logging.info("deleted {} messages".format(len(allNewIds)))

server.delete_messages(allNewIds)
server.expunge()
server.logout()  # quit imap
mailserver.quit()  # quit smtp

now = datetime.datetime.now()
print("Written to log. Last run was @ ", now.isoformat())
Esempio n. 23
0
class IMAPBot(object):
    IMAPBotError = IMAPBotError

    def __init__(self, host, username, password, ssl=True):
        self.server = IMAPClient(host, use_uid=True, ssl=ssl)
        self.server.login(username, password)

        if 'IDLE' not in self.server.capabilities():
            raise IMAPBotError('Sorry, this IMAP server does not support IDLE.')

        # the folder where processed emails go
        self.processed_folder = 'imapbot_processed'

        self.idle_timeout = 5  # seconds

        self._is_idle = False
        self._run = True

        self._create_folder(self.processed_folder)

    def check_mail(self):
        select_info = self.server.select_folder('INBOX')
        print '%d messages in INBOX' % select_info['EXISTS']

        messages = self.server.search(['UNSEEN'])
        messages = self.server.search(['NOT DELETED'])
        print "%d messages that haven't been seen" % len(messages)

        if not messages:
            return

        #response = self.server.fetch(messages, ['FLAGS', 'INTERNALDATE', 'RFC822.SIZE', 'ENVELOPE', 'RFC822.TEXT'])
        response = self.server.fetch(messages, ['FLAGS', 'ENVELOPE', 'RFC822.TEXT'])
        for message_id, data in response.iteritems():
            message = Message(message_id, data['ENVELOPE'], data['RFC822.TEXT'], data['FLAGS'])

            self.process(message)

    def complete(self, message):
        message_ids = [message.id]

        self.server.copy(message_ids, self.processed_folder)
        self.server.delete_messages(message_ids)
        self.server.expunge()

    def _create_folder(self, name):
        # make sure the folder doesn't already exist
        if self.server.folder_exists(name):
            return

        self.server.create_folder(name)

    def handle_message(self, message):
        print 'message id: {}, from: {}:'.format(message.id, message.envelope.get_email('from'))
        with open('message.txt', 'ab') as fh:
            fh.write('{}\n\n'.format(message.text))

        print message.plain or message.html or 'no message'

    def idle(self):
        if self._is_idle:
            return

        self.server.idle()

        self._is_idle = True

        return True  # this actually changed state

    def unidle(self):
        if not self._is_idle:
            return

        self.server.idle_done()

        self._is_idle = False

        return True  # this call actually changed state

    def process(self, message):
        self.handle_message(message)
        self.complete(message)

    def run(self):
        # process any mail that was in the inbox before coming online
        self.check_mail()

        # put the connection in idle mode so we get notifications
        self.idle()

        # loop forever looking for stuff
        while self._run:
            for message in self.server.idle_check(timeout=self.idle_timeout):
                if message[0] == 'OK':
                    continue

                with Unidle(self):
                    self.check_mail()

    def quit(self):
        self._run = False

        self.unidle()

        print self.server.logout()
Esempio n. 24
0
class MailToolbox(SourceFactory):
    def __init__(self,
                 hote_imap,
                 nom_utilisateur,
                 mot_de_passe,
                 dossier_cible='INBOX',
                 verify_peer=True,
                 use_secure_socket=True,
                 legacy_secure_protocol=False):

        super().__init__('IMAPFactory via {}'.format(hote_imap))

        self._ssl_context = SSLContext(
            protocol=PROTOCOL_TLS) if use_secure_socket else None
        self._use_secure_socket = use_secure_socket

        if verify_peer is False and use_secure_socket is True:
            # don't check if certificate hostname doesn't match target hostname
            self._ssl_context.check_hostname = False
            # don't check if the certificate is trusted by a certificate authority
            self._ssl_context.verify_mode = CERT_NONE

        if legacy_secure_protocol and use_secure_socket:
            self._ssl_context.options = OP_ALL

        self._hote_imap = Session.UNIVERSELLE.retranscrire(hote_imap)
        self._client = IMAPClient(host=self._hote_imap,
                                  port=993 if self._use_secure_socket else 143,
                                  ssl=self._use_secure_socket,
                                  ssl_context=self._ssl_context)
        self._nom_utilisateur = Session.UNIVERSELLE.retranscrire(
            nom_utilisateur)
        self._verify_peer = verify_peer
        self._dossier_cible = dossier_cible
        self._mot_de_passe = mot_de_passe

        self._client.login(
            self._nom_utilisateur,
            Session.UNIVERSELLE.retranscrire(self._mot_de_passe))

        self._echec = False

        MailToolbox.INSTANCES.append(self)

    @property
    def est_hors_service(self):
        return self._echec

    def reset(self):

        try:
            self._client.logout()
        except IMAPClientError as e:
            pass
        except OSError as e:
            pass

        try:
            self._client = IMAPClient(
                host=self._hote_imap,
                port=993 if self._use_secure_socket else 143,
                ssl=self._use_secure_socket,
                ssl_context=self._ssl_context)
            self._client.login(
                self._nom_utilisateur,
                Session.UNIVERSELLE.retranscrire(self._mot_de_passe))
        except IMAPClientError as e:
            logger.error(
                "Une erreur IMAP critique est survenue lors de la reconnexion. {msg_err}",
                msg_err=str(e))
            self._echec = True
            return

        self._echec = False

    @property
    def dossier_cible(self):
        return self._dossier_cible

    @dossier_cible.setter
    def dossier_cible(self, nouveau_dossier_cible):
        if isinstance(nouveau_dossier_cible, str):
            self._dossier_cible = nouveau_dossier_cible

    @property
    def hote_imap(self):
        return self._hote_imap

    @property
    def nom_utilisateur(self):
        return self._nom_utilisateur

    @staticmethod
    def fetch_instance(hote_imap, nom_utilisateur):
        hote_imap, nom_utilisateur = Session.UNIVERSELLE.retranscrire(
            hote_imap), Session.UNIVERSELLE.retranscrire(nom_utilisateur)

        for inst in MailToolbox.INSTANCES:
            if isinstance(
                    inst, MailToolbox
            ) is True and inst.nom_utilisateur == nom_utilisateur and inst.hote_imap == hote_imap:
                return inst
        return None

    def extraire(self, no_progress_bar=True):

        if self.est_hors_service is True:
            self.reset()

        try:
            self._client.select_folder(self._dossier_cible)
        except IMAPClientError as e:
            raise ExtractionSourceException('IMAPClientError: ' + str(e))
        except IMAPClientAbortError as e:
            raise ExtractionSourceException('IMAPClientAbortError: ' + str(e))
        except IMAPClientReadOnlyError as e:
            raise ExtractionSourceException('IMAPClientReadOnlyError: ' +
                                            str(e))
        finally:
            self._echec = True

        # fetch selectors are passed as a simple list of strings.
        responses = self._client.fetch(self._client.search(['NOT', 'DELETED']),
                                       ['UID', 'ENVELOPE', 'BODY', 'RFC822'])

        extractions = list()  # type: list[Mail]

        for id_response in tqdm(responses.keys(
        ), unit=' message') if no_progress_bar is False else responses.keys():

            email_message = email.message_from_bytes(
                responses[id_response][b'RFC822'])

            mail = Mail.from_message(email_message)

            mail.folder = self._dossier_cible
            mail.flags = responses[id_response][
                b'FLAGS'] if b'FLAGS' in responses[id_response].keys(
                ) else tuple()
            mail.bal_internal_id = id_response

            extractions.append(mail)

            mail.factory = self

        return sorted(extractions,
                      key=lambda x: x.date_received or datetime.now())

    def copier(self, mail, dossier_dest):
        """

        :param Mail mail:
        :param str dossier_dest:
        :return:
        """
        if self.est_hors_service is True:
            self.reset()

        try:
            if self._client.folder_exists(dossier_dest) is False:
                raise FileNotFoundError(
                    'Le dossier "{}" n\'existe pas sur le serveur IMAP distant !'
                )

            self._client.select_folder(mail.folder)

            self._client.copy([mail.bal_internal_id], dossier_dest)
        except IMAPClientError as e:
            raise ManipulationSourceException('IMAPClientError: ' + str(e))
        except IMAPClientAbortError as e:
            raise ManipulationSourceException('IMAPClientAbortError: ' +
                                              str(e))
        except IMAPClientReadOnlyError as e:
            raise ManipulationSourceException('IMAPClientReadOnlyError: ' +
                                              str(e))
        finally:
            self._echec = True

    def deplacer(self, mail, dossier_dest):

        if self.est_hors_service is True:
            self.reset()

        try:
            if self._client.folder_exists(dossier_dest) is False:
                raise FileNotFoundError(
                    'Le dossier "{}" n\'existe pas sur le serveur IMAP distant !'
                )

            self._client.select_folder(mail.folder)

            try:
                self._client.move([mail.bal_internal_id], dossier_dest)
            except CapabilityError as e:
                self.copier(mail, dossier_dest)
                self.supprimer(mail)
        except IMAPClientError as e:
            raise ManipulationSourceException('IMAPClientError: ' + str(e))
        except IMAPClientAbortError as e:
            raise ManipulationSourceException('IMAPClientAbortError: ' +
                                              str(e))
        except IMAPClientReadOnlyError as e:
            raise ManipulationSourceException('IMAPClientReadOnlyError: ' +
                                              str(e))
        finally:
            self._echec = True

    def supprimer(self, mail):

        if self.est_hors_service is True:
            self.reset()

        try:
            self._client.select_folder(mail.folder)

            self._client.delete_messages([mail.bal_internal_id], silent=True)
            self._client.expunge([mail.bal_internal_id])
        except IMAPClientError as e:
            raise ManipulationSourceException('IMAPClientError: ' + str(e))
        except IMAPClientAbortError as e:
            raise ManipulationSourceException('IMAPClientAbortError: ' +
                                              str(e))
        except IMAPClientReadOnlyError as e:
            raise ManipulationSourceException('IMAPClientReadOnlyError: ' +
                                              str(e))
        finally:
            self._echec = True
Esempio n. 25
0
 def remove_message(client: IMAPClient):
     client.select_folder(EmailFolders.IMPORTANT)
     client.delete_messages(next_uid_important)
     client.expunge()
Esempio n. 26
0
class Mail:
    """
    Wrapper for all needed methods to process the mails on the IMAP-server.

    Attributes
    ----------
    config : config.Config
        Configuration manager wrapping around the yaml-config-file.
    """

    # List of all Content-types of attachments that should be saved seperatly
    valid_ctypes = ['application/pdf']

    # Regexp that matches all allowed characters in a filename
    filesafe = re.compile(r'[\w\-\. ]')

    def __init__(self, config):
        config.checkParams('server', 'port', 'username', 'password', 'inbox',
                           'outbox', 'basepath', 'eml-to-pdf-path')
        host = config.get('server')
        port = config.get('port')
        username = config.get('username')
        password = config.get('password')
        inbox = config.get('inbox')
        self.outbox = config.get('outbox')
        self.basepath = config.get('basepath')
        if not os.path.exists(self.basepath): os.makedirs(self.basepath)
        self.emltopdf = config.get('eml-to-pdf-path')

        self.server = IMAPClient(host, port=port)
        result = self.server.login(username, password)
        print(result.decode('UTF-8'))
        self.server.select_folder(inbox)

    def check(self):
        """
        Checks for new mail to process on the server.
        """
        messages = self.server.search(['ALL'])
        if len(messages) < 1: return

        for id, data in self.server.fetch(messages, 'RFC822').items():
            mail = email.message_from_bytes(data[b'RFC822'])
            self.processMail(mail)

        self.server.copy(messages, self.outbox)
        self.server.delete_messages(messages)
        self.server.expunge()

    def getDate(self, mail):
        """
        Extracts the date from the mail header and rewrites it for use as the
        beginning of a filename.

        Parameters
        ----------
        mail : email
            Mail from which to extract the date.

        Returns
        -------
        The date reformatted to be used in a filename.
        """
        date = mail.get('Date')
        date = datetime.strptime(date[:-6], '%a, %d %b %Y %H:%M:%S')
        return date.strftime('%Y-%m-%d-%H-%M-%S')

    def processMail(self, mail):
        """
        Processes a specific mail by saving it.

        Parameters
        ----------
        mail : email
            The mail to process.
        """
        filename = '%s-%s.eml' % (self.getDate(mail), mail.get('Subject'))
        filename = self.validateFilename(filename)
        filename = os.path.join(self.basepath, filename)
        with open(filename, 'w') as f:
            generator = email.generator.Generator(f)
            generator.flatten(mail)

        if mail.is_multipart(): self.processAttachments(mail)

    def processAttachments(self, mail):
        """
        Processes all attachments of a mail.

        Parameters
        ----------
        mail : email
            The mail to process.
        """
        date = self.getDate(mail)
        for part in mail.walk():
            ctype = part.get_content_type()
            if not ctype in Mail.valid_ctypes: continue
            filename = '%s-attachment-%s' % (date, part.get_filename())
            filename = self.validateFilename(filename)
            filename = os.path.join(self.basepath, filename)
            with open(filename, 'wb') as f:
                f.write(part.get_payload(decode=True))

    def validateFilename(self, filename):
        """
        Makes a filename safe.

        Parameters
        ----------
        filename : str
            Filename to reformat.

        Returns
        -------
        The new filename, stripped of unsafe characters.
        """
        return ''.join([c for c in filename if Mail.filesafe.match(c)])

    def __del__(self):
        """
        Deconstructor disconnects from the IMAP-Server.
        """
        self.server.logout()
Esempio n. 27
0
class gmailPy(object):
    def __init__(self):
        self.IMAP_SERVER = 'imap.gmail.com'
        self.ssl = True
        self.myIMAPc = None
        self.response = None
        self.folders = []

    def login(self, username, password):
        self.myIMAPc = IMAPClient(self.IMAP_SERVER, ssl=self.ssl)
        self.myIMAPc.login(username, password)

    # Returns a list of all the folders for a particular account
    def get_folders(self):
        self.response = self.myIMAPc.list_folders()
        for item in self.response:
            self.folders.append(item[2].strip('u'))
        return self.folders

    # Returns the total number of messages in a folder
    def get_mail_count(self, folder='Inbox'):
        self.response = self.myIMAPc.select_folder(folder, True)
        return self.response['EXISTS']

    # Method to delete messages based on their size
    def delete_bigmail(self, folder='Inbox'):
        self.myIMAPc.select_folder(folder, False)
        # Gets all the message ids of the messages which are not deleted in the folder
        messages = self.myIMAPc.search(['NOT DELETED'])
        print "%d messages that aren't deleted" % len(messages)
        if len(messages) > 0:
            print "You can exit by entering 0 or pressing CTRL+C \n"
        else: print "There are no messages in the folder"
        # Gets the message sizes for all the message ids returned in previous step
        # Note: Just sends one request for all message ids with a return time < 10 ms
        self.response = self.myIMAPc.fetch(messages, ['RFC822.SIZE'])
        # Sorts the dictionary returned by fetch by size in descending order
        sorted_response = sorted(self.response.iteritems(), key=operator.itemgetter(1), reverse=True)
        count = 1
        try:
            for item in sorted_response:
                # Gets the biggest message including headers, body, etc.
                big_message = self.myIMAPc.fetch(item[0], ['RFC822'])
                for msgid, data in big_message.iteritems():
                    msg_string = data['RFC822']
                    # Parses the message string using email library
                    msg = email.message_from_string(msg_string)
                    val = dict(self.response[msgid])['RFC822.SIZE']
                    print 'ID %d: From: %s Date: %s' % (msgid, msg['From'], msg['date'])
                    print 'To: %s' % (msg['To'])
                    print 'Subject: %s' % (msg['Subject'])
                    print 'Size: %d bytes \n' % (val)
                    user_del = raw_input("Do you want to delete this message?(Y/N): ")
                    if user_del == 'Y':
                        self.delete_message(msgid)
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    elif user_del == '0':
                        print "Program exiting"
                        sys.exit()
                    else:
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    count += 1
        except KeyboardInterrupt:
            print "Program exiting"
            sys.exit()

    # Method to delete messages based on their size with a search criteria
    def delete_bigmail_search(self, folder='Inbox', command='', criteria=''):
        self.myIMAPc.select_folder(folder, False)
        # Gets all the message ids from the server based on the search criteria
        messages = self.myIMAPc.search('%s "%s"' % (command, criteria))
        print "%d messages that match --> %s: %s" % (len(messages), command, criteria)
        if len(messages) > 0:
            print "You can exit by entering 0 or pressing CTRL+C \n"
        else: print "There are no messages in that matched your search criteria"
        # Gets the message sizes for all the message ids returned in previous step
        # Note: Just sends one request for all message ids with a return time < 10 ms
        self.response = self.myIMAPc.fetch(messages, ['RFC822.SIZE'])
        # Sorts the messages in decending order of their sizes
        sorted_response = sorted(self.response.iteritems(), key=operator.itemgetter(1), reverse=True)
        count = 1
        try:
            for item in sorted_response:
                # Gets the entire content for the biggest message identified
                big_message = self.myIMAPc.fetch(item[0], ['RFC822'])
                for msgid, data in big_message.iteritems():
                    msg_string = data['RFC822']
                    msg = email.message_from_string(msg_string)
                    val = dict(self.response[msgid])['RFC822.SIZE']
                    print 'ID %d: From: %s Date: %s' % (msgid, msg['From'], msg['date'])
                    print 'To: %s' % (msg['To'])
                    print 'Subject: %s' % (msg['Subject'])
                    print 'Size: %d bytes \n' % (val)
                    user_del = raw_input("Do you want to delete this message?(Y/N): ")
                    if user_del == 'Y':
                        self.delete_message(msgid)
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    elif user_del == '0':
                        print "Program exiting"
                        sys.exit()
                    else:
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    count += 1

        except KeyboardInterrupt:
            print "Program exiting"
            sys.exit()

    # Deletes a message in the current folder based on msg id
    def delete_message(self, id):
        try:
            self.myIMAPc.delete_messages([id])
            self.myIMAPc.expunge()
            print "Message deleted"
        except IMAPClient.Error as err:
            print "Message deletion failed"
            print err

    # Renames a folder
    def rename_folder(self, oldfolder, newfolder):
        try:
            self.myIMAPc.rename_folder(oldfolder, newfolder)
            print "Folder %s renamed to %s" % (oldfolder, newfolder)
        except IMAPClient.Error as err:
            print "Folder renaming failed"
            print err

    # Creates a new folder
    def create_folder(self, folder):
        try:
            self.myIMAPc.create_folder(folder)
            print "New folder %s created" % folder
        except IMAPClient.Error as err:
            print "Folder creation failed"
            print err

    # Deletes a folder
    def delete_folder(self, folder):
        try:
            self.myIMAPc.delete_folder(folder)
            print "Folder %s deleted" % folder
        except IMAPClient.Error as err:
            print "Folder deletion failed"
            print err

    # Creates a new folder and copies the content from the two folders that need to be merged
    # Then deletes the old folders
    def merge_folders(self, merged_folder, folder_1, folder_2):
        try:
            self.create_folder(merged_folder)
            # Selects the folder with read/write permission
            self.myIMAPc.select_folder(folder_1, True)
            messages = self.myIMAPc.search(['NOT DELETED'])
            print "Moving %d messages from %s to %s" % (len(messages), folder_1, merged_folder)
            self.myIMAPc.copy(messages, merged_folder)
            self.myIMAPc.select_folder(folder_2, True)
            messages = self.myIMAPc.search(['NOT DELETED'])
            print "Moving %d messages from %s to %s" % (len(messages), folder_2, merged_folder)
            self.myIMAPc.copy(messages, merged_folder)
            print "Deleting %s and %s..." % (folder_1, folder_2)
            self.delete_folder(folder_1)
            self.delete_folder(folder_2)
            print "Merge folder operation succeeded"
        except IMAPClient.Error as err:
            print "Merge operation failed"
            print err

    def logout(self):
        self.myIMAPc.logout()