Commit | Line | Data |
---|---|---|
c9da760a PD |
1 | ''' |
2 | fix_imap_internaldate.py - Fix the INTERNALDATE field on IMAP servers | |
3 | ||
4 | Copyright (c) 2012 Intra2net AG | |
5 | Author: Plamen Dimitrov | |
6 | ||
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. | |
11 | ||
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. | |
c9da760a PD |
16 | ''' |
17 | ||
c9da760a | 18 | import csv |
8fe4e3ff PD |
19 | import argparse, configparser |
20 | import logging | |
8a9d4c89 | 21 | from mail_date_parser import MailDateParser |
c9da760a | 22 | from mail_iterator import MailIterator |
8301e589 | 23 | from caching_data import CachingData |
c9da760a PD |
24 | |
25 | def main(): | |
97bd6bea PD |
26 | """Interprets command arguments and initializes configuration and logger. |
27 | Then begins mail synchronization.""" | |
648f0037 | 28 | |
97bd6bea | 29 | # parse arguments |
648f0037 PD |
30 | parser = argparse.ArgumentParser(description="Fix the INTERNALDATE field on IMAP servers. " |
31 | "Small tool to fix the IMAP internaldate " | |
32 | "in case it's too much off compared to the last date " | |
33 | "stored in the received lines.") | |
34 | parser.add_argument('-u', '--update', dest='test_mode', action='store_false', | |
35 | default=True, help='update all e-mails and exit test mode') | |
c9da760a | 36 | |
8fe4e3ff | 37 | # config and logging setup |
c9da760a | 38 | config = load_configuration() |
8fe4e3ff | 39 | prepare_logger(config) |
97bd6bea | 40 | args = parser.parse_args() |
648f0037 | 41 | if(args.test_mode): |
f9fd9b29 | 42 | logging.info("Testing mode initiated. No message will be modified on the server.") |
648f0037 | 43 | else: |
f9fd9b29 | 44 | logging.info("Update mode initiated. Messages will be modified.") |
3b81023f | 45 | |
97bd6bea PD |
46 | # proceed to main functionality |
47 | synchronize_csv(config, args.test_mode) | |
48 | ||
49 | return | |
50 | ||
51 | def load_configuration(): | |
52 | """Loads the script configuration from a file or creates such.""" | |
53 | config = configparser.RawConfigParser() | |
54 | success = config.read('confscript.cfg') | |
55 | if(len(success)==0): | |
56 | config.add_section('basic_settings') | |
57 | config.set('basic_settings', 'file_log_level', logging.INFO) | |
58 | config.set('basic_settings', 'console_log_level', logging.INFO) | |
59 | config.set('basic_settings', 'imap_server', 'imap.company.com') | |
60 | config.set('basic_settings', 'tolerance', 30) | |
61 | with open('confscript.cfg', 'w') as configfile: | |
62 | config.write(configfile) | |
63 | return config | |
64 | ||
65 | def prepare_logger(config): | |
66 | """Sets up the logging functionality""" | |
67 | ||
68 | # reset the log | |
69 | with open('fix_imap_internaldate.log', 'w'): | |
70 | pass | |
71 | ||
72 | # add basic configuration | |
73 | logging.basicConfig(filename='fix_imap_internaldate.log', | |
74 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
75 | level=config.getint('basic_settings', 'file_log_level')) | |
76 | ||
77 | # add a handler for a console output | |
78 | console = logging.StreamHandler() | |
79 | console.setLevel(config.getint('basic_settings', 'console_log_level')) | |
80 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
81 | console.setFormatter(formatter) | |
82 | logging.getLogger('').addHandler(console) | |
83 | return | |
84 | ||
85 | def synchronize_csv(config, test_mode): | |
86 | """Iterates through csv list of users and synchronizes their messages.""" | |
87 | ||
88 | # initialize loop permanent data | |
89 | caching_data = CachingData() | |
90 | date_parser = MailDateParser() | |
c9da760a | 91 | server = config.get('basic_settings', 'imap_server') |
8fe4e3ff | 92 | tolerance = config.getint('basic_settings', 'tolerance') * 60 |
c9da760a | 93 | |
97bd6bea PD |
94 | # iterate through the users in the csv data |
95 | user_reader = csv.DictReader(open("userdata.csv", "r"), delimiter=',') | |
c9da760a PD |
96 | for user in user_reader: |
97 | try: | |
98 | session = MailIterator(server, user['username'], user['password']) | |
99 | except UserWarning as ex: | |
100 | logging.error(ex) | |
101 | continue | |
102 | for mailbox in session: | |
103 | try: | |
7a1d4c35 | 104 | box = caching_data.retrieve_cached_mailbox(mailbox[0], mailbox[1], user['username']) |
c9da760a | 105 | mail_ids = session.fetch_messages() |
3b81023f | 106 | new_ids = box.synchronize(mail_ids, tolerance) |
6f2bc406 | 107 | logging.info("%s new messages out of %s found in %s.", len(new_ids), len(mail_ids), box.name) |
c9da760a PD |
108 | except UserWarning as ex: |
109 | logging.error(ex) | |
110 | continue | |
8301e589 | 111 | for mid in new_ids: |
c9da760a PD |
112 | try: |
113 | fetched_internal_date = session.fetch_internal_date(mid) | |
8a9d4c89 | 114 | internal_date = date_parser.extract_internal_date(fetched_internal_date) |
c9da760a | 115 | fetched_received_date = session.fetch_received_date(mid) |
8a9d4c89 | 116 | received_date = date_parser.extract_received_date(fetched_received_date) |
c9da760a | 117 | if(received_date==""): |
d5ccfbec | 118 | logging.debug("No received date could be found in message uid: %s - mailbox: %s - user: %s.", |
8a9d4c89 | 119 | mid.decode('iso-8859-1'), box.name, box.owner) |
7a1d4c35 | 120 | box.no_received_field += 1 |
c9da760a PD |
121 | continue |
122 | except UserWarning as ex: | |
123 | logging.error(ex) | |
124 | continue | |
8a9d4c89 | 125 | if(date_parser.compare_dates(received_date, internal_date, tolerance)): |
36f4d196 TJ |
126 | 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.", |
127 | mid.decode('iso-8859-1'), box.name, box.owner, | |
128 | internal_date.strftime("%d %b %Y %H:%M:%S"), | |
129 | received_date.strftime("%d %b %Y %H:%M:%S"), | |
130 | fetched_received_date.split("Received:")[1]) | |
97bd6bea | 131 | if(test_mode==0): |
c9da760a | 132 | try: |
7a1d4c35 | 133 | session.update_message(mid, box.name, received_date) |
c9da760a PD |
134 | except UserWarning as ex: |
135 | logging.error(ex) | |
136 | continue | |
36f4d196 | 137 | |
8301e589 | 138 | # count total emails for every user and mailbox |
7a1d4c35 | 139 | box.date_conflicts += 1 |
36f4d196 | 140 | |
8301e589 | 141 | # if all messages were successfully fixed confirm caching |
97bd6bea | 142 | if(not test_mode): |
7a1d4c35 | 143 | box.confirm_change() |
97bd6bea | 144 | |
8301e589 | 145 | # final report on date conflicts |
3b81023f | 146 | caching_data.report_conflicts() |
8fe4e3ff PD |
147 | return |
148 | ||
c9da760a PD |
149 | if(__name__ == "__main__"): |
150 | main() |