#!/usr/bin/python import imaplib import ConfigParser import os import time import sys import re if sys.stdout.isatty(): imaplib.Debug = 4 sleep_time=15 config_file='/usr/local/bin/imap2imap.conf' mid_r=re.compile('Message-ID:\s<(.*)>.*') date_r=re.compile('\d+\s\(INTERNALDATE\s\"(.*)\"\)') def config_sample(): print( """ Sample config: [source] hostname=outlook.office365.com username=microsoftsucks@example.com password=hunter12 trash=Deleted Items #movetotrash=yes #delete=yes [destination] hostname=imap.gmail.com username=dontbeevil@gmail.com password=hunter12 """) def config(): global config_file conf = {'source':{},'destination':{}} c = ConfigParser.ConfigParser() try: c.read([os.path.expanduser(config_file)]) except Exception as e: print("Unable to read config file! Error: %s" % str(e)) config_sample() sys.exit(1) try: conf['source'].update({'server' : c.get('source', 'hostname')}) conf['source'].update({'username' : c.get('source', 'username')}) conf['source'].update({'password' : c.get('source', 'password')}) try: conf['source'].update({'trash' : c.get('source', 'trash')}) except ConfigParser.NoOptionError: conf['source'].update({'trash' : 'Trash'}) try: conf['source'].update({'inbox' : c.get('source', 'inbox')}) except ConfigParser.NoOptionError: conf['source'].update({'inbox': 'INBOX'}) try: delete = c.get('source', 'delete') if delete.lower() == 'true' or delete.lower() == 'yes': conf['source'].update({'delete' : True }) else: conf['source'].update({'delete' : False }) except ConfigParser.NoOptionError: conf['source'].update({'delete': False}) try: delete = c.get('source', 'movetotrash') if delete.lower() == 'true' or delete.lower() == 'yes': conf['source'].update({'movetotrash' : True }) else: conf['source'].update({'movetotrash' : False }) except ConfigParser.NoOptionError: conf['source'].update({'movetotrash': False}) conf['destination'].update({'server' : c.get('destination', 'hostname')}) conf['destination'].update({'username' : c.get('destination', 'username')}) conf['destination'].update({'password' : c.get('destination', 'password')}) try: conf['destination'].update({'inbox' : c.get('destination', 'inbox')}) except ConfigParser.NoOptionError: conf['destination'].update({'inbox': 'INBOX'}) except Exception as e: print("Unable to get a config item! Error: %s" % str(e)) config_sample() sys.exit(1) return conf def connect(conf): try: s_h = conf['source']['server'] s_u = conf['source']['username'] s_p = conf['source']['password'] d_h = conf['destination']['server'] d_u = conf['destination']['username'] d_p = conf['destination']['password'] except Exception as e: print("Bad config! Error: %s",str(e)) try: print("Connecting to (source) %s" % s_h) src = imaplib.IMAP4_SSL(s_h) print("Logging in as (source) %s" % s_u) src.login(s_u, s_p) print("Connecting to (destination) %s" % d_h) dst = imaplib.IMAP4_SSL(d_h) print("Logging in as (destination) %s" % d_u) dst.login(d_u, d_p) except Exception as e: print("Unable to connect! Error: %s" % str(e)) sys.exit(1) return src,dst def delete_message(con,message_id): print("Deleting message id %s" % message_id) try: r, res = con.store(message_id, '+FLAGS', '(\\Deleted)') if r != 'OK': print("Failed to delete message id %s; response: %s, %s" % (message_id,r,res)) return False except Exception as e: print("Failed to delete message id %s; Error: %s" % (message_id,str(e))) return False def expunge(con): try: r, res = con.expunge() if r != 'OK': print("Failed to expunge mailbox; response: %s, %s" % (r,res)) return False except Exception as e: print("Failed to expunge mailbox; Error: %s" % str(e)) return True def copy_message(con,message_id,directory): print("Copying message id %s to '%s'" % (message_id, directory)) try: r, res = con.copy(message_id,trash) if r != 'OK': print("Failed to copy message id %s to '%s'; response: %s, %s" % (message_id,directory,r,res)) return False except Exception as e: print("Failed to copy message id %s to '%s'; Error: %s" % (message_id,directory,str(e))) return False return True def date_message(con,message_id): global date_r try: r, res = con.fetch(message_id, '(INTERNALDATE)') date = date_r.match(res[0]).group(1) if r != 'OK': print("Failed to get INTERNALDATE of message id %s, Falling back to 'NOW'; response: %s, %s" % (message_id,r,res)) return time.time() except Exception as e: print("Failed to get INTERNALDATE of message id %s, Falling back to 'NOW'; Error: %s" % (message_id,str(e))) return time.time() return date def find_dupes(src_con,dst_con,message_id): global mid_r print("Searching for duplicates for message id %s" % message_id) try: r,res = src_con.fetch(message_id, '(BODY[HEADER.FIELDS (MESSAGE-ID)])') if r != 'OK': return False mid = mid_r.match(res[0][1]).group(1) r,[res] = dst_con.search(None,'(HEADER Message-ID "%s")' % mid) if res: print("Message with Message-ID <%s> is already on the destination. Skipping..." % mid) return True else: return False except Exception as e: print("Unable to fetch Message-ID for message id %s" % message_id) return False conf = config() src_inbox = conf['source']['inbox'] dst_inbox = conf['destination']['inbox'] trash = conf['source']['trash'] delete = conf['source']['delete'] movetotrash = conf['source']['movetotrash'] while True: try: src_con,dst_con = connect(conf) src_con.select(src_inbox) dst_con.select(dst_inbox) sys.stdout.flush() r, [message_ids] = src_con.search(None, 'ALL') message_ids = message_ids.split() message_count = len(message_ids) print("Found %d messages in '%s'" % (message_count,src_inbox)) for message_id in message_ids: print("Fetching message id %s" % message_id) r, data = src_con.fetch(message_id, '(RFC822)') date_message(src_con,message_id) if not find_dupes(src_con,dst_con,message_id): print("Uploading message id %s" % message_id) r, res = dst_con.append(dst_inbox, '', imaplib.Time2Internaldate(date_message(src_con,message_id)), str(''.join(data[0][1]))) else: r = 'OK' #not ideal if r == 'OK': if movetotrash: print("Moving message id %s to '%s'" % (message_id, trash)) if copy_message(src_con,message_id,trash): delete_message(src_con,message_id) elif delete: delete_message(src_con,message_id) else: print("Failed to upload message id %s; response %s, %s" % (message_id,r,res)) sys.stdout.flush() expunge(src_con) print("Finished all (%d) messages, sleeping for %d seconds" % (message_count,sleep_time)) time.sleep(sleep_time) except Exception as e: print("Error: %s" % str(e)) finally: try: src_con.close() src_con.logout() dst_con.close() dst_con.logout() except: pass