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() ]
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!")
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() ]
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()
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()
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
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
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}
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)
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!')
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()
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)
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)
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)
def remove_message(client: IMAPClient): client.select_folder(EmailFolders.URGENT) client.delete_messages(next_uid_urgent) client.expunge()
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
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())
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()
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
def remove_message(client: IMAPClient): client.select_folder(EmailFolders.IMPORTANT) client.delete_messages(next_uid_important) client.expunge()
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()
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()