This file is indexed.

/usr/lib/python2.7/dist-packages/txdav/caldav/datastore/scheduling/imip/inbound.py is in calendarserver 5.2+dfsg-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
##
# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

"""
Inbound IMIP mail handling for Calendar Server
"""

from twext.enterprise.dal.record import fromTable
from twext.enterprise.dal.syntax import Delete
from twext.enterprise.queue import WorkItem
from twext.internet.gaiendpoint import GAIEndpoint
from twext.python.log import Logger, LegacyLogger

from twisted.application import service
from twisted.internet import protocol, defer, ssl
from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.mail import pop3client, imap4
from twisted.mail.smtp import messageid

from twistedcaldav.config import config
from twistedcaldav.ical import Property, Component

from txdav.caldav.datastore.scheduling.imip.scheduler import IMIPScheduler
from txdav.caldav.datastore.scheduling.imip.smtpsender import SMTPSender
from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
from txdav.common.datastore.sql_tables import schema

import datetime
import email.utils


log = Logger()

#
# Monkey patch imap4.log so it doesn't emit useless logging,
# specifically, "Unhandled unsolicited response" nonsense.
#
class IMAPLogger(LegacyLogger):
    def msg(self, *message, **kwargs):
        if message and message[0].startswith("Unhandled unsolicited response:"):
            return

        super(IMAPLogger, self).msg(self, *message, **kwargs)

imap4.log = IMAPLogger()


""" SCHEMA:
create table IMIP_REPLY_WORK (
  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ') not null,
  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
  ORGANIZER                     varchar(255) not null,
  ATTENDEE                      varchar(255) not null,
  ICALENDAR_TEXT                text         not null
);
create table IMIP_POLLING_WORK (
  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ') not null,
  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
);
"""

class IMIPReplyWork(WorkItem, fromTable(schema.IMIP_REPLY_WORK)):

    @inlineCallbacks
    def doWork(self):
        calendar = Component.fromString(self.icalendarText)
        yield injectMessage(self.transaction, self.organizer, self.attendee, calendar)



class IMIPPollingWork(WorkItem, fromTable(schema.IMIP_POLLING_WORK)):

    # FIXME: purge all old tokens here
    group = "imip_polling"

    @inlineCallbacks
    def doWork(self):

        # Delete all other work items
        yield Delete(From=self.table, Where=None).on(self.transaction)

        mailRetriever = self.transaction._mailRetriever
        if mailRetriever is not None:
            try:
                yield mailRetriever.fetchMail()
            except Exception, e:
                log.error("Failed to fetch mail (%s)" % (e,))
            finally:
                yield mailRetriever.scheduleNextPoll()



class MailRetriever(service.Service):

    def __init__(self, store, directory, settings, reactor=None):
        self.store = store
        self.settings = settings
        if reactor is None:
            from twisted.internet import reactor
        self.reactor = reactor

        # If we're using our dedicated account on our local server, we're free
        # to delete all messages that arrive in the inbox so as to not let
        # cruft build up
        self.deleteAllMail = shouldDeleteAllMail(config.ServerHostName,
            settings.Server, settings.Username)
        self.mailReceiver = MailReceiver(store, directory)
        mailType = settings['Type']
        if mailType.lower().startswith('pop'):
            self.factory = POP3DownloadFactory
        else:
            self.factory = IMAP4DownloadFactory

        contextFactory = None
        if settings["UseSSL"]:
            contextFactory = ssl.ClientContextFactory()
        self.point = GAIEndpoint(self.reactor, settings.Server,
            settings.Port, contextFactory=contextFactory)


    def fetchMail(self):
        return self.point.connect(self.factory(self.settings, self.mailReceiver,
            self.deleteAllMail))


    @inlineCallbacks
    def scheduleNextPoll(self, seconds=None):
        if seconds is None:
            seconds = self.settings["PollingSeconds"]
        yield scheduleNextMailPoll(self.store, seconds)



def shouldDeleteAllMail(serverHostName, inboundServer, username):
    """
    Given the hostname of the calendar server, the hostname of the pop/imap
    server, and the username we're using to access inbound mail, determine
    whether we should delete all messages in the inbox or whether to leave
    all unprocessed messages.

    @param serverHostName: the calendar server hostname (config.ServerHostName)
    @type serverHostName: C{str}
    @param inboundServer: the pop/imap server hostname
    @type inboundServer: C{str}
    @param username: the name of the account we're using to retrieve mail
    @type username: C{str}
    @return: True if we should delete all messages from the inbox, False otherwise
    @rtype: C{boolean}
    """
    return (
        inboundServer in (serverHostName, "localhost") and
        username == "com.apple.calendarserver"
    )



@inlineCallbacks
def scheduleNextMailPoll(store, seconds):
    txn = store.newTransaction()
    notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds)
    log.debug("Scheduling next mail poll: %s" % (notBefore,))
    yield txn.enqueue(IMIPPollingWork, notBefore=notBefore)
    yield txn.commit()



class MailReceiver(object):

    NO_TOKEN = 0
    UNKNOWN_TOKEN = 1
    MALFORMED_TO_ADDRESS = 2
    NO_ORGANIZER_ADDRESS = 3
    REPLY_FORWARDED_TO_ORGANIZER = 4
    INJECTION_SUBMITTED = 5
    INCOMPLETE_DSN = 6
    UNKNOWN_FAILURE = 7

    def __init__(self, store, directory):
        self.store = store
        self.directory = directory


    def checkDSN(self, message):
        # returns (isdsn, action, icalendar attachment)

        report = deliveryStatus = calBody = None

        for part in message.walk():
            contentType = part.get_content_type()
            if contentType == "multipart/report":
                report = part
                continue
            elif contentType == "message/delivery-status":
                deliveryStatus = part
                continue
            elif contentType == "message/rfc822":
                #original = part
                continue
            elif contentType == "text/calendar":
                calBody = part.get_payload(decode=True)
                continue

        if report is not None and deliveryStatus is not None:
            # we have what appears to be a dsn

            lines = str(deliveryStatus).split("\n")
            for line in lines:
                lower = line.lower()
                if lower.startswith("action:"):
                    # found action:
                    action = lower.split(' ')[1]
                    break
            else:
                action = None

            return True, action, calBody

        else:
            # not a dsn
            return False, None, None


    def _extractToken(self, text):
        try:
            pre, _ignore_post = text.split('@')
            pre, token = pre.split('+')
            return token
        except ValueError:
            return None


    @inlineCallbacks
    def processDSN(self, calBody, msgId):
        calendar = Component.fromString(calBody)
        # Extract the token (from organizer property)
        organizer = calendar.getOrganizer()
        token = self._extractToken(organizer)
        if not token:
            log.error("Mail gateway can't find token in DSN %s" % (msgId,))
            return

        txn = self.store.newTransaction()
        result = (yield txn.imipLookupByToken(token))
        yield txn.commit()
        try:
            # Note the results are returned as utf-8 encoded strings
            organizer, attendee, _ignore_icaluid = result[0]
        except:
            # This isn't a token we recognize
            log.error("Mail gateway found a token (%s) but didn't "
                           "recognize it in message %s"
                           % (token, msgId))
            returnValue(self.UNKNOWN_TOKEN)

        calendar.removeAllButOneAttendee(attendee)
        calendar.getOrganizerProperty().setValue(organizer)
        for comp in calendar.subcomponents():
            if comp.name() == "VEVENT":
                comp.addProperty(Property("REQUEST-STATUS",
                    ["5.1", "Service unavailable"]))
                break
        else:
            # no VEVENT in the calendar body.
            # TODO: what to do in this case?
            pass

        log.warn("Mail gateway processing DSN %s" % (msgId,))
        txn = self.store.newTransaction()
        yield txn.enqueue(IMIPReplyWork, organizer=organizer, attendee=attendee,
            icalendarText=str(calendar))
        yield txn.commit()
        returnValue(self.INJECTION_SUBMITTED)


    @inlineCallbacks
    def processReply(self, msg):
        # extract the token from the To header
        _ignore_name, addr = email.utils.parseaddr(msg['To'])
        if addr:
            # addr looks like: server_address+token@example.com
            token = self._extractToken(addr)
            if not token:
                log.error("Mail gateway didn't find a token in message "
                               "%s (%s)" % (msg['Message-ID'], msg['To']))
                returnValue(self.NO_TOKEN)
        else:
            log.error("Mail gateway couldn't parse To: address (%s) in "
                           "message %s" % (msg['To'], msg['Message-ID']))
            returnValue(self.MALFORMED_TO_ADDRESS)

        txn = self.store.newTransaction()
        result = (yield txn.imipLookupByToken(token))
        yield txn.commit()
        try:
            # Note the results are returned as utf-8 encoded strings
            organizer, attendee, _ignore_icaluid = result[0]
        except:
            # This isn't a token we recognize
            log.error("Mail gateway found a token (%s) but didn't "
                           "recognize it in message %s"
                           % (token, msg['Message-ID']))
            returnValue(self.UNKNOWN_TOKEN)

        for part in msg.walk():
            if part.get_content_type() == "text/calendar":
                calBody = part.get_payload(decode=True)
                break
        else:
            # No icalendar attachment
            log.warn("Mail gateway didn't find an icalendar attachment "
                          "in message %s" % (msg['Message-ID'],))

            toAddr = None
            fromAddr = attendee[7:]
            if organizer.startswith("mailto:"):
                toAddr = organizer[7:]
            elif organizer.startswith("urn:uuid:"):
                guid = organizer[9:]
                record = self.directory.recordWithGUID(guid)
                if record and record.emailAddresses:
                    toAddr = list(record.emailAddresses)[0]

            if toAddr is None:
                log.error("Don't have an email address for the organizer; "
                               "ignoring reply.")
                returnValue(self.NO_ORGANIZER_ADDRESS)

            settings = config.Scheduling["iMIP"]["Sending"]
            smtpSender = SMTPSender(settings.Username, settings.Password,
                settings.UseSSL, settings.Server, settings.Port)

            del msg["From"]
            msg["From"] = fromAddr
            del msg["Reply-To"]
            msg["Reply-To"] = fromAddr
            del msg["To"]
            msg["To"] = toAddr
            log.warn("Mail gateway forwarding reply back to organizer")
            yield smtpSender.sendMessage(fromAddr, toAddr, messageid(), msg)
            returnValue(self.REPLY_FORWARDED_TO_ORGANIZER)

        # Process the imip attachment; inject to calendar server

        log.debug(calBody)
        calendar = Component.fromString(calBody)
        event = calendar.mainComponent()

        calendar.removeAllButOneAttendee(attendee)
        organizerProperty = calendar.getOrganizerProperty()
        if organizerProperty is None:
            # ORGANIZER is required per rfc2446 section 3.2.3
            log.warn("Mail gateway didn't find an ORGANIZER in REPLY %s"
                          % (msg['Message-ID'],))
            event.addProperty(Property("ORGANIZER", organizer))
        else:
            organizerProperty.setValue(organizer)

        if not calendar.getAttendees():
            # The attendee we're expecting isn't there, so add it back
            # with a SCHEDULE-STATUS of SERVICE_UNAVAILABLE.
            # The organizer will then see that the reply was not successful.
            attendeeProp = Property("ATTENDEE", attendee,
                params={
                    "SCHEDULE-STATUS": iTIPRequestStatus.SERVICE_UNAVAILABLE,
                }
            )
            event.addProperty(attendeeProp)

            # TODO: We have talked about sending an email to the reply-to
            # at this point, to let them know that their reply was missing
            # the appropriate ATTENDEE.  This will require a new localizable
            # email template for the message.

        txn = self.store.newTransaction()
        yield txn.enqueue(IMIPReplyWork, organizer=organizer, attendee=attendee,
            icalendarText=str(calendar))
        yield txn.commit()
        returnValue(self.INJECTION_SUBMITTED)


    # returns a deferred
    def inbound(self, message):
        """
        Given the text of an incoming message, parse and process it.
        The possible return values are:

        NO_TOKEN - there was no token in the To address
        UNKNOWN_TOKEN - there was an unknown token in the To address
        MALFORMED_TO_ADDRESS - we could not parse the To address at all
        NO_ORGANIZER_ADDRESS - no ics attachment and no email to forward to
        REPLY_FORWARDED_TO_ORGANIZER - no ics attachment, but reply forwarded
        INJECTION_SUBMITTED - looks ok, was submitted as a work item
        INCOMPLETE_DSN - not enough in the DSN to go on
        UNKNOWN_FAILURE - any error we aren't specifically catching

        @param message: The body of the email
        @type message: C{str}
        @return: Deferred firing with one of the above action codes
        """
        try:
            msg = email.message_from_string(message)

            isDSN, action, calBody = self.checkDSN(msg)
            if isDSN:
                if action == 'failed' and calBody:
                    # This is a DSN we can handle
                    return self.processDSN(calBody, msg['Message-ID'])
                else:
                    # It's a DSN without enough to go on
                    log.error("Mail gateway can't process DSN %s"
                                   % (msg['Message-ID'],))
                    return succeed(self.INCOMPLETE_DSN)

            log.info("Mail gateway received message %s from %s to %s" %
                (msg['Message-ID'], msg['From'], msg['To']))

            return self.processReply(msg)

        except Exception, e:
            # Don't let a failure of any kind stop us
            log.error("Failed to process message: %s" % (e,))
        return succeed(self.UNKNOWN_FAILURE)



@inlineCallbacks
def injectMessage(txn, organizer, attendee, calendar):

    try:
        scheduler = IMIPScheduler(txn, None)
        results = (yield scheduler.doSchedulingDirectly("iMIP", attendee, [organizer, ], calendar,))
        log.info("Successfully injected iMIP response from %s to %s" % (attendee, organizer))
    except Exception, e:
        log.error("Failed to inject iMIP response (%s)" % (e,))
        raise

    returnValue(results)



#
# POP3
#

class POP3DownloadProtocol(pop3client.POP3Client):
    log = Logger()

    allowInsecureLogin = False

    def serverGreeting(self, greeting):
        self.log.debug("POP servergreeting")
        pop3client.POP3Client.serverGreeting(self, greeting)
        login = self.login(self.factory.settings["Username"],
            self.factory.settings["Password"])
        login.addCallback(self.cbLoggedIn)
        login.addErrback(self.cbLoginFailed)


    def cbLoginFailed(self, reason):
        self.log.error("POP3 login failed for %s" %
            (self.factory.settings["Username"],))
        return self.quit()


    def cbLoggedIn(self, result):
        self.log.debug("POP loggedin")
        return self.listSize().addCallback(self.cbGotMessageSizes)


    def cbGotMessageSizes(self, sizes):
        self.log.debug("POP gotmessagesizes")
        downloads = []
        for i in range(len(sizes)):
            downloads.append(self.retrieve(i).addCallback(self.cbDownloaded, i))
        return defer.DeferredList(downloads).addCallback(self.cbFinished)


    @inlineCallbacks
    def cbDownloaded(self, lines, id):
        self.log.debug("POP downloaded message %d" % (id,))
        actionTaken = (yield self.factory.handleMessage("\r\n".join(lines)))

        if self.factory.deleteAllMail:
            # Delete all mail we see
            self.log.debug("POP deleting message %d" % (id,))
            self.delete(id)
        else:
            # Delete only mail we've processed
            if actionTaken == MailReceiver.INJECTION_SUBMITTED:
                self.log.debug("POP deleting message %d" % (id,))
                self.delete(id)


    def cbFinished(self, results):
        self.log.debug("POP finished")
        return self.quit()



class POP3DownloadFactory(protocol.ClientFactory):
    log = Logger()

    protocol = POP3DownloadProtocol

    def __init__(self, settings, mailReceiver, deleteAllMail):
        self.settings = settings
        self.mailReceiver = mailReceiver
        self.deleteAllMail = deleteAllMail
        self.noisy = False


    def clientConnectionLost(self, connector, reason):
        self.connector = connector
        self.log.debug("POP factory connection lost")


    def clientConnectionFailed(self, connector, reason):
        self.connector = connector
        self.log.info("POP factory connection failed")


    def handleMessage(self, message):
        self.log.debug("POP factory handle message")
        # self.log.debug(message)
        return self.mailReceiver.inbound(message)



#
# IMAP4
#


class IMAP4DownloadProtocol(imap4.IMAP4Client):
    log = Logger()

    def serverGreeting(self, capabilities):
        self.log.debug("IMAP servergreeting")
        return self.authenticate(self.factory.settings["Password"]
            ).addCallback(self.cbLoggedIn
            ).addErrback(self.ebAuthenticateFailed)


    def ebLogError(self, error):
        self.log.error("IMAP Error: {err}", err=error)


    def ebAuthenticateFailed(self, reason):
        self.log.debug("IMAP authenticate failed for {name}, trying login",
            name=self.factory.settings["Username"])
        return self.login(self.factory.settings["Username"],
            self.factory.settings["Password"]
            ).addCallback(self.cbLoggedIn
            ).addErrback(self.ebLoginFailed)


    def ebLoginFailed(self, reason):
        self.log.error("IMAP login failed for {name}", name=self.factory.settings["Username"])
        self.transport.loseConnection()


    def cbLoggedIn(self, result):
        self.log.debug("IMAP logged in")
        self.select("Inbox").addCallback(self.cbInboxSelected)


    def cbInboxSelected(self, result):
        self.log.debug("IMAP Inbox selected")
        self.search(imap4.Query(unseen=True)).addCallback(self.cbGotSearch)


    def cbGotSearch(self, results):
        if results:
            ms = imap4.MessageSet()
            for n in results:
                ms.add(n)
            self.fetchUID(ms).addCallback(self.cbGotUIDs)
        else:
            self.cbClosed(None)


    def cbGotUIDs(self, results):
        self.messageUIDs = [result['UID'] for result in results.values()]
        self.messageCount = len(self.messageUIDs)
        self.log.debug("IMAP Inbox has {count} unseen messages", count=self.messageCount)
        if self.messageCount:
            self.fetchNextMessage()
        else:
            # No messages; close it out
            self.close().addCallback(self.cbClosed)


    def fetchNextMessage(self):
        # self.log.debug("IMAP in fetchnextmessage")
        if self.messageUIDs:
            nextUID = self.messageUIDs.pop(0)
            messageListToFetch = imap4.MessageSet(nextUID)
            self.log.debug("Downloading message %d of %d (%s)" %
                (self.messageCount - len(self.messageUIDs), self.messageCount,
                nextUID))
            self.fetchMessage(messageListToFetch, True).addCallback(
                self.cbGotMessage, messageListToFetch).addErrback(
                    self.ebLogError)
        else:
            self.log.debug("Seeing if anything new has arrived")
            # Go back and see if any more messages have come in
            self.expunge().addCallback(self.cbInboxSelected)


    @inlineCallbacks
    def cbGotMessage(self, results, messageList):
        self.log.debug("IMAP in cbGotMessage")
        try:
            messageData = results.values()[0]['RFC822']
        except IndexError:
            # results will be empty unless the "twistedmail-imap-flags-anywhere"
            # patch from http://twistedmatrix.com/trac/ticket/1105 is applied
            self.log.error("Skipping empty results -- apply twisted patch!")
            self.fetchNextMessage()
            return

        actionTaken = (yield self.factory.handleMessage(messageData))
        if self.factory.deleteAllMail:
            # Delete all mail we see
            yield self.cbFlagDeleted(messageList)
        else:
            # Delete only mail we've processed; the rest are left flagged Seen
            if actionTaken == MailReceiver.INJECTION_SUBMITTED:
                yield self.cbFlagDeleted(messageList)
            else:
                self.fetchNextMessage()


    def cbFlagDeleted(self, messageList):
        self.addFlags(messageList, ("\\Deleted",),
            uid=True).addCallback(self.cbMessageDeleted, messageList)


    def cbMessageDeleted(self, results, messageList):
        self.log.debug("Deleted message")
        self.fetchNextMessage()


    def cbClosed(self, results):
        self.log.debug("Mailbox closed")
        self.logout().addCallback(
            lambda _: self.transport.loseConnection())


    def rawDataReceived(self, data):
        # self.log.debug("RAW RECEIVED: {data}", data=data)
        imap4.IMAP4Client.rawDataReceived(self, data)


    def lineReceived(self, line):
        # self.log.debug("RECEIVED: {line}", line=line)
        imap4.IMAP4Client.lineReceived(self, line)


    def sendLine(self, line):
        # self.log.debug("SENDING: {line}", line=line)
        imap4.IMAP4Client.sendLine(self, line)



class IMAP4DownloadFactory(protocol.ClientFactory):
    log = Logger()

    protocol = IMAP4DownloadProtocol

    def __init__(self, settings, mailReceiver, deleteAllMail):
        self.log.debug("Setting up IMAPFactory")

        self.settings = settings
        self.mailReceiver = mailReceiver
        self.deleteAllMail = deleteAllMail
        self.noisy = False


    def buildProtocol(self, addr):
        p = protocol.ClientFactory.buildProtocol(self, addr)
        username = self.settings["Username"]
        p.registerAuthenticator(imap4.CramMD5ClientAuthenticator(username))
        p.registerAuthenticator(imap4.LOGINAuthenticator(username))
        p.registerAuthenticator(imap4.PLAINAuthenticator(username))
        return p


    def handleMessage(self, message):
        self.log.debug("IMAP factory handle message")
        # self.log.debug(message)
        return self.mailReceiver.inbound(message)


    def clientConnectionLost(self, connector, reason):
        self.connector = connector
        self.log.debug("IMAP factory connection lost")


    def clientConnectionFailed(self, connector, reason):
        self.connector = connector
        self.log.warn("IMAP factory connection failed")