2 fix_imap_internaldate.py - Fix the INTERNALDATE field on IMAP servers
4 Copyright (c) 2012 Intra2net AG
5 Author: Plamen Dimitrov
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
20 import argparse, configparser
22 from mail_date_parser import MailDateParser
23 from mail_iterator import MailIterator
24 from caching_data import CachingData
27 """Iterates through csv list of users and synchronize their messages"""
29 parser = argparse.ArgumentParser(description="Fix the INTERNALDATE field on IMAP servers. "
30 "Small tool to fix the IMAP internaldate "
31 "in case it's too much off compared to the last date "
32 "stored in the received lines.")
33 parser.add_argument('-u', '--update', dest='test_mode', action='store_false',
34 default=True, help='update all e-mails and exit test mode')
35 args = parser.parse_args()
37 # config and logging setup
38 config = load_configuration()
39 prepare_logger(config)
41 date_parser = MailDateParser()
42 caching_data = CachingData()
43 logging.warning("Cache version %s loaded.", caching_data.version)
45 logging.info("Testing mode initiated. No message will be modified on the server.")
47 logging.info("Update mode initiated. Messages will be modified.")
48 user_reader = csv.DictReader(open("userdata.csv", "r"), delimiter=',')
50 # server name is stored in the config
51 server = config.get('basic_settings', 'imap_server')
52 # tolerance is now in seconds
53 tolerance = config.getint('basic_settings', 'tolerance') * 60
55 for user in user_reader:
57 session = MailIterator(server, user['username'], user['password'])
58 except UserWarning as ex:
61 for mailbox in session:
63 box = caching_data.retrieve_cached_mailbox(mailbox[0], mailbox[1], user['username'])
64 mail_ids = session.fetch_messages()
65 new_ids = box.synchronize(mail_ids, tolerance)
66 logging.info("%s new messages out of %s found in %s.", len(new_ids), len(mail_ids), box.name)
67 except UserWarning as ex:
72 fetched_internal_date = session.fetch_internal_date(mid)
73 internal_date = date_parser.extract_internal_date(fetched_internal_date)
74 fetched_received_date = session.fetch_received_date(mid)
75 received_date = date_parser.extract_received_date(fetched_received_date)
76 if(received_date==""):
77 logging.debug("No received date could be found in message uid: %s - mailbox: %s - user: %s.",
78 mid.decode('iso-8859-1'), box.name, box.owner)
79 box.no_received_field += 1
81 except UserWarning as ex:
84 if(date_parser.compare_dates(received_date, internal_date, tolerance)):
85 #print(received_date, internal_date)
86 if(args.test_mode==0):
88 session.update_message(mid, box.name, received_date)
89 except UserWarning as ex:
93 logging.warning("Date conflict found in message uid: %s - mailbox: %s - user: %s.\nInternal date %s is different from received date %s from RECEIVED header:\n%s.",
94 mid.decode('iso-8859-1'), box.name, box.owner,
95 internal_date.strftime("%d %b %Y %H:%M:%S"),
96 received_date.strftime("%d %b %Y %H:%M:%S"),
97 fetched_received_date.split("Received:")[1])
98 # count total emails for every user and mailbox
99 box.date_conflicts += 1
100 # if all messages were successfully fixed confirm caching
101 if(not args.test_mode):
104 # final report on date conflicts
105 caching_data.report_conflicts()
107 def load_configuration():
108 """Loads the script configuration from a file or creates such."""
109 config = configparser.RawConfigParser()
110 success = config.read('confscript.cfg')
112 config.add_section('basic_settings')
113 config.set('basic_settings', 'file_log_level', logging.INFO)
114 config.set('basic_settings', 'console_log_level', logging.INFO)
115 config.set('basic_settings', 'imap_server', 'imap.company.com')
116 config.set('basic_settings', 'tolerance', 30)
117 #config.set('Basic settings', 'bool', 'true')
118 with open('confscript.cfg', 'w') as configfile:
119 config.write(configfile)
122 def prepare_logger(config):
123 """Sets up the logging functionality"""
126 with open('fix_imap_internaldate.log', 'w'):
129 # add basic configuration
130 logging.basicConfig(filename='fix_imap_internaldate.log',
131 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
132 level=config.getint('basic_settings', 'file_log_level'))
134 # add a handler for a console output
135 console = logging.StreamHandler()
136 console.setLevel(config.getint('basic_settings', 'console_log_level'))
137 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
138 console.setFormatter(formatter)
139 logging.getLogger('').addHandler(console)
144 if(__name__ == "__main__"):