[PATCH] Fix message corruption with concurrent flag changes

X Ryl boite.pour.spam at gmail.com
Mon Feb 4 20:10:22 GMT 2013


Applied in my next branch, if no one objects, I'll apply to the official
repo next branch tomorrow.

On Mon, Feb 4, 2013 at 5:52 AM, Austin Clements <amdragon at mit.edu> wrote:

> Ping.
>
> I've been running with this patch for some time now and haven't received
> any more corrupted email deliveries (though my logs show many instances
> where I would have received a corrupted email with the unpatched code).
>
> On Sat, 29 Dec 2012, Austin Clements <amdragon at MIT.EDU> wrote:
> > IMAPFolder.getmessage incorrectly assumes that the 'data' returned by
> > imapobj.uid('fetch', ...) will always be of the form
> >
> >   [(fetch-info, message-body)]
> >
> > However, this call returns a list of *all* pending untagged FETCH
> > responses, not just the requested FETCH.  If any message flags were
> > changed in the selected IMAP folder since the last command (by another
> > client or another thread in OfflineIMAP itself), the IMAP server will
> > issue unsolicited FETCH responses indicating these flag changes
> > (RFC3501, section 7).  When this happens, 'data' will look like, for
> > example
> >
> >   ['1231 (FLAGS (\\Seen) UID 5300)',
> >    '1238 (FLAGS (\\Seen) UID 5318)',
> >    ('1242 (UID 5325 BODY[] {7976}', message-body)]
> >
> > Unfortunately, getmessage retrieves the message body as data[0][1],
> > which in this example is just the string "2".  This is what ultimately
> > gets stored in the maildir file, resulting in a message consisting
> > exclusively of a single ASCII digit.
> >
> > Ideally, either imaplib2 or getmessage would parse the fetch responses
> > to find the response for the requested UID.  However, since IMAP only
> > specifies unilateral FETCH responses for flag changes, the body fetch
> > will be the only tuple in the response; hence this patch simply finds
> > the element of 'data' that is a tuple (aborting if there is more than
> > one tuple) and uses that.
> >
> > Multi-threaded OfflineIMAP with IDLE or holdconnectionopen is
> > particularly susceptible to this problem because flag changes synced
> > back to the IMAP server on one thread will appear as unsolicited FETCH
> > responses on another thread if it happens to have the same folder
> > selected.  This can also happen without IDLE or holdconnectionopen or
> > even in single-threaded OfflineIMAP with concurrent access from other
> > IMAP clients (webmail clients, etc.), though the window for the bug is
> > much smaller.
> >
> > Signed-off-by: Austin Clements <amdragon at mit.edu>
> > ---
> >
> > I posted a preliminary version of this patch on December 7th.  I
> > tweaked it slightly in the following days and have been running this
> > continuously since then without any problems.
> >
> > Since the context of this patch has changed dramatically between maint
> > and master, this is based on master.  maint also has this bug,
> > however, and if people want I can write a separate backported patch.
> >
> >  offlineimap/folder/IMAP.py |   10 +++++++++-
> >  1 file changed, 9 insertions(+), 1 deletion(-)
> >
> > diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
> > index 75520c2..6b0ed04 100644
> > --- a/offlineimap/folder/IMAP.py
> > +++ b/offlineimap/folder/IMAP.py
> > @@ -226,7 +226,15 @@ class IMAPFolder(BaseFolder):
> >                      fails_left -= 1
> >                      if not fails_left:
> >                          raise e
> > -            if data == [None] or res_type != 'OK':
> > +            # Remove unsolicited FETCH responses caused by flag
> > +            # changes from concurrent connections.  These appear as
> > +            # strings in 'data', while the BODY response appears as a
> > +            # tuple.  The closing ')' on the BODY response also
> > +            # appears as a stray string, which this also removes.
> > +            # This should leave exactly one response in 'data'.
> > +            if res_type == 'OK':
> > +                data = [res for res in data if not isinstance(res, str)]
> > +            if data == [None] or res_type != 'OK' or len(data) != 1:
> >                  #IMAP server says bad request or UID does not exist
> >                  severity = OfflineImapError.ERROR.MESSAGE
> >                  reason = "IMAP server '%s' failed to fetch message UID
> '%d'."\
> > --
> > 1.7.10.4
>
> _______________________________________________
> OfflineIMAP-project mailing list
> OfflineIMAP-project at lists.alioth.debian.org
> http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/offlineimap-project
>
> OfflineIMAP homepage: http://software.complete.org/offlineimap
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/offlineimap-project/attachments/20130204/c2623ce6/attachment-0003.html>


More information about the OfflineIMAP-project mailing list