Esempio n. 1
0
    def test_search_mail_errors(self):
        username, password = self.create_imap_user()
        imapconn = self.create_basic_imap_object(username, password)
        self.assertEqual(imapconn.connect(), IMAP.Retval(True, 'Logged in'))

        self.assertRaises(RuntimeError,
                          imapconn.search_mails,
                          mailbox='DoesNotExist',
                          criteria='ALL')  # tests do_select_mailbox
        self.assertRaises(AttributeError,
                          imapconn.search_mails,
                          'DoesNotExist',
                          criteria='ALL')  # tests do_select_mailbox
        self.assertRaises(KeyError, imapconn.search_mails,
                          criteria='ALL')  # tests do_select_mailbox

        errors = [
            IMAP.Retval(
                False,
                'SEARCH command error: BAD [b\'Error in IMAP command UID SEARCH: Unknown argument DOESNOTEXIST\']'
            ),
            IMAP.Retval(
                False,
                'b\'Error in IMAP command UID SEARCH: Unknown argument THISCOMMANDDOESNOTEXIST\'\n\n'
                'This error may have been caused by a syntax error in the criteria: "ThisCommandDoesNotExist"\n'
                'Please refer to the documentation for more information about search criteria syntax..\n'
                'https://imapclient.readthedocs.io/en/master/#imapclient.IMAPClient.search'
            ),
        ]
        self.assertIn(
            imapconn.search_mails(mailbox='INBOX',
                                  criteria='ThisCommandDoesNotExist'), errors)

        self.assertEqual(imapconn.disconnect(),
                         IMAP.Retval(True, 'Logging out'))
Esempio n. 2
0
 def test_connect_manual_logout(self):
     username, password = self.create_imap_user()
     imapconn = self.create_basic_imap_object(username, password)
     self.assertEqual(imapconn.connect(logout=False),
                      IMAP.Retval(True, 'Logged in'))
     self.assertEqual(imapconn.disconnect(),
                      IMAP.Retval(True, 'Logging out'))
Esempio n. 3
0
 def test_connect_simple_plaintext(self):
     username, password = self.create_imap_user()
     self.assertEqual(
         IMAP(logger=self.logger,
              server=self.INTEGRATION_ADDR_IMAPSERVER,
              port=self.INTEGRATION_PORT_IMAP,
              username=username,
              password=password).connect(logout=True),
         IMAP.Retval(True, 'Logging out'))
Esempio n. 4
0
    def test_connect_error_auth_failed(self):
        username, password = self.create_imap_user()

        expect = str(b'[AUTHENTICATIONFAILED] Authentication failed.')
        self.assertEqual(
            IMAP(logger=self.logger,
                 server=self.INTEGRATION_ADDR_IMAPSERVER,
                 port=self.INTEGRATION_PORT_IMAP,
                 username=username,
                 password='******').connect(logout=True),
            IMAP.Retval(False, expect))
Esempio n. 5
0
    def test_select_mailbox(self):
        username, password = self.create_imap_user()
        imapconn = self.create_basic_imap_object(username, password)
        self.assertEqual(imapconn.connect(), IMAP.Retval(True, 'Logged in'))

        result = imapconn.select_mailbox(mailbox='INBOX')
        self.assertEqual(
            result.data['FLAGS'],
            ('\\Answered', '\\Flagged', '\\Deleted', '\\Seen', '\\Draft'))

        self.assertEqual(imapconn.disconnect(),
                         IMAP.Retval(True, 'Logging out'))
Esempio n. 6
0
 def test_connect_imaps(self):
     username, password = self.create_imap_user()
     self.assertEqual(
         IMAP(
             logger=self.logger,
             server=self.INTEGRATION_ADDR_IMAPSERVER,
             port=self.INTEGRATION_PORT_IMAPS,
             starttls=False,
             imaps=True,
             tlsverify=False,  # TODO test tls verification?
             username=username,
             password=password).connect(logout=True),
         IMAP.Retval(True, 'Logging out'))
Esempio n. 7
0
    def test_select_mailbox_nonexisting_mailbox(self):
        username, password = self.create_imap_user()
        imapconn = self.create_basic_imap_object(username, password)
        self.assertEqual(imapconn.connect(), IMAP.Retval(True, 'Logged in'))

        result = imapconn.select_mailbox(mailbox='DoesNotExist')
        self.assertEqual(
            result,
            IMAP.Retval(False,
                        'select failed: Mailbox doesn\'t exist: DoesNotExist'))

        self.assertEqual(imapconn.disconnect(),
                         IMAP.Retval(True, 'Logging out'))
Esempio n. 8
0
 def test_connect_error_refused(self):
     username, password = self.create_imap_user()
     self.assertIn(
         IMAP(
             logger=self.logger,
             server=self.INTEGRATION_ADDR_IMAPSERVER,
             port=1337,
             starttls=False,
             imaps=True,
             tlsverify=False,  # TODO test tls verification?
             username=username,
             password=password).connect(),
         [
             IMAP.Retval(False, '[Errno 111] Connection refused'),
             IMAP.Retval(False, '[Errno 61] Connection refused')
         ])
Esempio n. 9
0
    def test_add_mail(self):
        username, password = self.create_imap_user()
        imapconn = self.create_basic_imap_object(username, password)
        self.assertEqual(imapconn.connect(), IMAP.Retval(True, 'Logged in'))

        example_date = datetime.datetime(
            2009, 4, 5, 11, 0, 5, 0,
            imapclient.fixed_offset.FixedOffset(2 * 60))
        self.assertTrue(
            imapconn.add_mail(
                mailbox='INBOX',
                message=self.create_email(headers={'Subject': 'Testmäil'}),
                flags=['FLAG', 'WAVE']).code)
        self.assertTrue(
            imapconn.add_mail(mailbox='INBOX',
                              message=self.create_email(headers={
                                  'Subject': 'Testmäil'
                              }).get_native(),
                              flags=['\\Seen']).code)

        self.assertEqual(
            imapconn.fetch_mails(
                uids=[1], mailbox='INBOX').data[1].get_header('Subject'),
            'Testmäil')
        self.assertEqual(
            imapconn.fetch_mails(
                uids=[2], mailbox='INBOX').data[2].get_header('Subject'),
            'Testmäil')

        self.assertEqual(
            imapconn.add_mail(mailbox='DoesNotExist',
                              message=self.create_email(),
                              flags=['FLAG', 'WAVE'],
                              msg_time=example_date),
            IMAP.Retval(
                False,
                'append failed: [TRYCREATE] Mailbox doesn\'t exist: DoesNotExist'
            ))

        self.assertEqual(imapconn.disconnect(),
                         IMAP.Retval(True, 'Logging out'))
Esempio n. 10
0
    def test_search_mail(self):
        username, password = self.create_imap_user()
        imapconn = self.create_basic_imap_object(username, password)
        self.assertEqual(imapconn.connect(), IMAP.Retval(True, 'Logged in'))

        # Adding some mails to search for
        example_date = datetime.datetime(
            2009, 4, 5, 11, 0, 5, 0,
            imapclient.fixed_offset.FixedOffset(2 * 60))
        self.assertTrue(
            imapconn.add_mail(mailbox='INBOX',
                              message=self.create_email(),
                              flags=['FLAG', 'WAVE']).code)
        self.assertTrue(
            imapconn.add_mail(mailbox='INBOX',
                              message=self.create_email(),
                              flags=['\\Seen']).code)
        self.assertTrue(
            imapconn.add_mail(mailbox='INBOX',
                              message=self.create_email(),
                              flags=['FLAG', 'WAVE'],
                              msg_time=example_date).code)

        self.assertEqual(
            imapconn.search_mails(mailbox='INBOX', criteria='ALL'),
            IMAP.Retval(True, [1, 2, 3]))
        self.assertEqual(
            imapconn.search_mails(mailbox='INBOX', criteria='UNSEEN'),
            IMAP.Retval(True, [1, 3]))
        self.assertEqual(
            imapconn.search_mails(mailbox='INBOX', criteria='SEEN'),
            IMAP.Retval(True, [2]))
        self.assertEqual(
            imapconn.search_mails(mailbox='INBOX',
                                  criteria='SINCE 13-Apr-2015'),
            IMAP.Retval(True, [1, 2]))

        self.assertEqual(imapconn.disconnect(), (True, 'Logging out'))
Esempio n. 11
0
    def test_list_mailboxes(self):
        username, password = self.create_imap_user()
        imapconn = self.create_basic_imap_object(username, password)

        self.assertEqual(imapconn.connect(), (True, 'Logged in'))

        expect = [{
            'delimiter': '/',
            'flags': ['\\HasNoChildren', '\\Trash'],
            'name': 'Trash'
        }, {
            'delimiter': '/',
            'flags': ['\\HasNoChildren', '\\Drafts'],
            'name': 'Drafts'
        }, {
            'delimiter': '/',
            'flags': ['\\HasNoChildren', '\\Sent'],
            'name': 'Sent'
        }, {
            'delimiter': '/',
            'flags': ['\\HasNoChildren', '\\Junk'],
            'name': 'Junk'
        }, {
            'delimiter': '/',
            'flags': ['\\HasNoChildren'],
            'name': 'INBOX'
        }]
        self.assertEqual(imapconn.list_mailboxes(), IMAP.Retval(True, expect))
        self.assertEqual(imapconn.disconnect(),
                         IMAP.Retval(True, 'Logging out'))

        # Test exception handling
        self.assertEqual(
            imapconn.list_mailboxes(),
            IMAP.Retval(
                False,
                'command LIST illegal in state LOGOUT, only allowed in states AUTH, SELECTED'
            ))
    def create_basic_imap_object(self,
                                 username,
                                 password,
                                 starttls=False,
                                 imaps=True,
                                 test=None):

        imapconn = IMAP(
            logger=self.logger,
            server=self.INTEGRATION_ADDR_IMAPSERVER,
            port=self.INTEGRATION_PORT_IMAPS,
            starttls=starttls,
            imaps=imaps,
            tlsverify=False,  # TODO
            username=username,
            password=password,
            test=test,
            timeout=5)
        return imapconn
Esempio n. 13
0
def main():
    program_name = 'tabellarius'
    allowed_log_levels = ['DEBUG', 'ERROR', 'INFO']

    if python_version[0] < 3:
        print('ERROR: Your need to use Python 3 to run {0}! Your version: {1}'.
              format(program_name, python_version))

    parser = ArgumentParser(
        prog=program_name,
        description='A mail-sorting tool that is less annoying')

    # General args
    parser.add_argument(
        '-V',
        action='version',
        version='%(prog)s {version}'.format(version=__version__))
    parser.add_argument(
        '-t',
        '--test',
        action='store_true',
        dest='test',
        help=
        'Run in test mode, run read-only IMAP commands only (WARNING: Bare Implementation!)',
        default=None)
    parser.add_argument('-l',
                        '--loglevel',
                        action='store',
                        dest='log_level',
                        help='Override log level setting ({0})'.format(
                            ', '.join(allowed_log_levels)),
                        default='')
    parser.add_argument(
        '--gpg-homedir',
        action='store',
        dest='gpg_homedir',
        help='Override gpg home dir setting (default: ~/.gnupg/)',
        default='~/.gnupg/')
    parser.add_argument(
        '--sleep',
        action='store',
        dest='imap_sleep_time',
        help='Sleep time between IMAP parsing for e-mails (default: 2)',
        type=int,
        default=2)
    parser.add_argument(
        '--confdir',
        action='store',
        dest='confdir',
        help='Directory to search for configuration files (default: config/)',
        default='config/',
        required=True)

    parser_results = parser.parse_args()
    confdir = parser_results.confdir
    test = parser_results.test

    log_level = parser_results.log_level.upper()
    if log_level and log_level not in allowed_log_levels:
        print(
            'ERROR: LOG_LEVEL {0} is not supported, supported log levels are: {1}'
            .format(log_level, ', '.join(allowed_log_levels)))
        exit(127)

    gpg_homedir = parser_results.gpg_homedir
    imap_sleep_time = parser_results.imap_sleep_time

    # Config Parsing
    cfg_parser = ConfigParser()
    cfg_parser.load(confdir)
    validation_error = cfg_parser.validate()

    if validation_error:
        print('ERROR: Failed to parse config directory. Config is invalid: {}'.
              format(validation_error.message))
        exit(127)

    config = cfg_parser.dump()
    if test is not None:
        config['settings']['test'] = test

    # Logging
    logconfig = config.get('settings', {}).get('logging', {})
    if log_level:
        logconfig['root']['level'] = log_level
    logger = Helper().create_logger(program_name, logconfig)

    # Let's start working now
    logger.debug('Starting new instance of %s', program_name)
    logger.debug('Raw configuration: %s', config)

    # Setup gnupg if necessary
    for acc, acc_settings in config.get('accounts').items():
        if 'password_enc' in acc_settings:
            import gnupg

            gpg_homedir = config.get('settings').get('gpg_homedir',
                                                     gpg_homedir)

            gpg = gnupg.GPG(
                homedir=gpg_homedir,
                use_agent=config.get('settings').get('gpg_use_agent', False),
                binary=config.get('settings').get('gpg_binary', 'gpg2'))
            gpg.encoding = 'utf-8'

    # Initialize connection pools
    imap_pool = {}
    for acc_id, acc_settings in sorted(config.get('accounts').items()):
        # Check whether we got a plaintext password
        acc_password = acc_settings.get('password')
        if not acc_password:
            # Switch to GPG-encrypted password
            enc_password = None

            # Shall we use gpg-agent or use Python's getpass to retreive the plain text password
            if config.get('settings').get('gpg_use_agent', False):
                enc_password = gpg.decrypt(
                    message=acc_settings.get('password_enc'))

                if not enc_password.ok:
                    logger.error('%s: Failed to decrypt GPG message: %s',
                                 acc_settings.get('username'),
                                 enc_password.status)
                    logger.debug('%s: GPG error: %s',
                                 acc_settings.get('username'),
                                 enc_password.stderr)
                    exit(1)
                acc_password = str(enc_password)
            else:
                acc_password = getpass(
                    'Please enter the IMAP password for {0} ({1}): '.format(
                        acc_id, acc_settings.get('username')))

        logger.info('%s: Setting up IMAP connection',
                    acc_settings.get('username'))
        imap_pool[acc_id] = IMAP(logger=logger,
                                 server=acc_settings.get('server'),
                                 port=acc_settings.get('port', 143),
                                 starttls=acc_settings.get('starttls', True),
                                 imaps=acc_settings.get('imaps', False),
                                 tlsverify=acc_settings.get('tlsverify', True),
                                 username=acc_settings.get('username'),
                                 password=acc_password,
                                 test=test)
        connect = imap_pool[acc_id].connect()

        if not connect.code:
            logger.error(
                '%s: Failed to login, please check your account credentials: %s',
                acc_settings.get('username'), connect.data)
            exit(127)
        else:
            logger.info('%s: Sucessfully logged in!',
                        acc_settings.get('username'))

    logger.info('Entering mail-sorting loop')
    while True:
        for acc_id, acc_settings in sorted(config.get('accounts').items()):
            pre_inbox = acc_settings.get('pre_inbox', 'PreInbox')
            pre_inbox_search = acc_settings.get('pre_inbox_search', 'ALL')
            sort_mailbox = acc_settings.get('sort_mailbox', None)

            try:
                if not imap_pool[acc_id].mailbox_exists(pre_inbox).data:
                    imap_pool[acc_id].logger.info(
                        '%s: Destination mailbox %s doesn\'t exist, creating it for you',
                        acc_settings.get('username'), pre_inbox)

                    result = imap_pool[acc_id].create_mailbox(
                        mailbox=pre_inbox)
                    if not result.code:
                        imap_pool[acc_id].logger.error(
                            '%s: Failed to create the mailbox %s: %s',
                            acc_settings.get('username'), pre_inbox,
                            result.data)
                        return result

                mail_uids = imap_pool[acc_id].search_mails(
                    mailbox=pre_inbox,
                    criteria=pre_inbox_search,
                    autocreate_mailbox=True).data
                if not mail_uids:
                    logger.debug('%s: No mails found to sort',
                                 acc_settings.get('username'))
                    continue

                mails = imap_pool[acc_id].fetch_mails(uids=mail_uids,
                                                      mailbox=pre_inbox).data
                mails_without_match = []
                for uid, mail in mails.items():
                    match = False

                    if mail.get_message_id() is None:
                        logger.error(
                            'Mail with uid={} and subject=\'{}\' doesn\'t have a message-id! Abort..'
                            .format(uid, mail.get_header('subject')))
                        exit(1)

                    for filter_name, filter_settings in Helper().sort_dict(
                            config.get('filters').get(acc_id)).items():
                        mail_filter = MailFilter(logger=logger,
                                                 imap=imap_pool[acc_id],
                                                 mail=mail,
                                                 config=filter_settings,
                                                 mailbox=pre_inbox)
                        match = mail_filter.check_rules_match()
                        if match:
                            break

                    if match:
                        continue

                    if sort_mailbox:
                        mails_without_match.append(uid)
                    else:
                        imap_pool[acc_id].set_mailflags(
                            uids=[uid],
                            mailbox=pre_inbox,
                            flags=acc_settings.get('unmatched_mail_flags',
                                                   ['\\FLAGGED']))

                if sort_mailbox and mails_without_match:
                    logger.info(
                        '%s: Moving mails that did not match any filter to %s',
                        acc_settings.get('username'), sort_mailbox)

                    for uid in mails_without_match:
                        mail = mails[uid]
                        imap_pool[acc_id].move_mail(
                            message_ids=[mail.get_message_id()],
                            source=pre_inbox,
                            destination=sort_mailbox,
                            set_flags=[])

            # except IMAPClient.Error as e:
            #    logger.error('%s: Catching exception: %s. This is bad and I am sad. Going to sleep for a few seconds and trying again..',
            #                 acc_settings.get('username'), e)
            #    sleep(10)

            except Exception as e:
                trace_info = exc_info()
                logger.error(
                    '%s: Catching unknown exception: %s. Showing stack trace and going to die..',
                    acc_settings.get('username'), e)

                print_exception(*trace_info)
                del trace_info

                exit(1)

        logger.debug(
            'All accounts checked, going to sleep for %s seconds before checking again..',
            imap_sleep_time)
        sleep(imap_sleep_time)