コード例 #1
0
ファイル: mech_smtp.py プロジェクト: inpos/serpent
 def validateTo(self, user):
     self.messageid = smtp.messageid().split('@')[0].strip('<')
     try:
         rules.validateTo(self, user)
     except:
         raise
     else:
         msg = {
                    'from': [user.orig.local, user.orig.domain],
                    'rcpt': [user.dest.local, user.dest.domain],
                    'transaction_id': self.messageid,
                    'id': smtp.messageid().split('@')[0].strip('<')
                    }
         return lambda: SmtpMessage(msg)
コード例 #2
0
    def test_postModerated(self):
        """
        L{INewsStorage.postRequest} forwards a message to the moderator if it
        does not include an I{Approved} header.
        """
        group = "example.group"
        moderator = "*****@*****.**"
        mailhost = "127.0.0.1"
        sender = "*****@*****.**"
        articleID = messageid()
        storage = self.getStorage(
            [group], {group: [moderator]}, mailhost, sender)
        message = self.getUnapprovedMessage(articleID, group)
        result = storage.postRequest(message)

        def cbModerated(ignored):
            self.assertEqual(len(self._email), 1)
            self.assertEqual(self._email[0][0], mailhost)
            self.assertEqual(self._email[0][1], sender)
            self.assertEqual(self._email[0][2], [moderator])
            self._checkModeratorMessage(
                self._email[0][3], sender, moderator, group, message)
            self.assertEqual(self._email[0][4], None)
            self.assertEqual(self._email[0][5], 25)
            exists = storage.articleExistsRequest(articleID)
            exists.addCallback(self.assertFalse)
            return exists
        result.addCallback(cbModerated)
        return result
コード例 #3
0
ファイル: bounce.py プロジェクト: kuna/controller_server
def generateBounce(message, failedFrom, failedTo, transcript=''):
    if not transcript:
        transcript = '''\
I'm sorry, the following address has permanent errors: %(failedTo)s.
I've given up, and I will not retry the message again.
''' % vars()

    boundary = "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX')
    failedAddress = rfc822.AddressList(failedTo)[0][1]
    failedDomain = failedAddress.split('@', 1)[1]
    messageID = smtp.messageid(uniq='bounce')
    ctime = time.ctime(time.time())

    fp = StringIO.StringIO()
    fp.write(BOUNCE_FORMAT % vars())
    orig = message.tell()
    message.seek(2, 0)
    sz = message.tell()
    message.seek(0, orig)
    if sz > 10000:
        while 1:
            line = message.readline()
            if len(line) <= 1:
                break
            fp.write(line)
    else:
        fp.write(message.read())
    return '', failedFrom, fp.getvalue()
コード例 #4
0
    def test_postModerated(self):
        """
        L{INewsStorage.postRequest} forwards a message to the moderator if it
        does not include an I{Approved} header.
        """
        group = "example.group"
        moderator = "*****@*****.**"
        mailhost = "127.0.0.1"
        sender = "*****@*****.**"
        articleID = messageid()
        storage = self.getStorage(
            [group], {group: [moderator]}, mailhost, sender)
        message = self.getUnapprovedMessage(articleID, group)
        result = storage.postRequest(message)

        def cbModerated(ignored):
            self.assertEquals(len(self._email), 1)
            self.assertEquals(self._email[0][0], mailhost)
            self.assertEquals(self._email[0][1], sender)
            self.assertEquals(self._email[0][2], [moderator])
            self._checkModeratorMessage(
                self._email[0][3], sender, moderator, group, message)
            self.assertEquals(self._email[0][4], None)
            self.assertEquals(self._email[0][5], 25)
            exists = storage.articleExistsRequest(articleID)
            exists.addCallback(self.assertFalse)
            return exists
        result.addCallback(cbModerated)
        return result
コード例 #5
0
ファイル: bounce.py プロジェクト: AlexanderHerlan/syncpy
def generateBounce(message, failedFrom, failedTo, transcript=''):
    if not transcript:
        transcript = '''\
I'm sorry, the following address has permanent errors: %(failedTo)s.
I've given up, and I will not retry the message again.
''' % vars()

    boundary = "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX')
    failedAddress = rfc822.AddressList(failedTo)[0][1]
    failedDomain = failedAddress.split('@', 1)[1]
    messageID = smtp.messageid(uniq='bounce')
    ctime = time.ctime(time.time())

    fp = StringIO.StringIO()
    fp.write(BOUNCE_FORMAT % vars())
    orig = message.tell()
    message.seek(2, 0)
    sz = message.tell()
    message.seek(0, orig)
    if sz > 10000:
        while 1:
            line = message.readline()
            if len(line)<=1:
                break
            fp.write(line)
    else:
        fp.write(message.read())
    return '', failedFrom, fp.getvalue()
コード例 #6
0
def compose_plain_email(fromEmail, toEmail, content, headers):
    msg = MIMEText(content)

    # Setup the mail headers
    for (header, value) in headers.items():
        msg[header] = value

    msg.set_charset('utf-8')

    headkeys = [k.lower() for k in headers.keys()]

    # Add required headers if not present
    if "message-id" not in headkeys:
        msg["Message-ID"] = messageid()
    if "date" not in headkeys:
        msg["Date"] = rfc822date()
    if "from" not in headkeys and "sender" not in headkeys:
        msg["From"] = fromEmail
    if "to" not in headkeys and "cc" not in headkeys and "bcc" not in headkeys:
        msg["To"] = toEmail
    if "reply-to" not in headkeys:
        msg["Reply-To"] = SUPPORT_ADDRESS
    if "user-agent" not in headkeys:
        msg["User-Agent"] = USER_AGENT

    return msg.as_string()
コード例 #7
0
    def sendEmail(self, url, attempt, email, _sendEmail=_sendEmail):
        """
        Send an email for the given L{_PasswordResetAttempt}.

        @type url: L{URL}
        @param url: The URL of the password reset page.

        @type attempt: L{_PasswordResetAttempt}
        @param attempt: An L{Item} representing a particular user's attempt to
        reset their password.

        @type email: C{str}
        @param email: The email will be sent to this address.
        """

        host = url.netloc.split(':', 1)[0]
        from_ = 'reset@' + host

        body = file(sibpath(__file__, 'reset.rfc2822')).read()
        body %= {
            'from': from_,
            'to': email,
            'date': rfc822.formatdate(),
            'message-id': smtp.messageid(),
            'link': url.child(attempt.key)
        }

        _sendEmail(from_, email, body)
コード例 #8
0
def send_plain_email(smtphost, username, password, fromEmail, toEmail, content, headers,
                     senderDomainName=None, port=25, requireSSL=True):
    requireAuth = bool(password)
    msg = MIMEText(content)

    # Setup the mail headers
    for (header, value) in headers.items():
        msg[header] = value

    headkeys = [k.lower() for k in headers.keys()]

    # Add required headers if not present
    if "message-id" not in headkeys:
        msg["Message-ID"] = messageid()
    if "date" not in headkeys:
        msg["Date"] = rfc822date()
    if "from" not in headkeys and "sender" not in headkeys:
        msg["From"] = fromEmail
    if "to" not in headkeys and "cc" not in headkeys and "bcc" not in headkeys:
        msg["To"] = toEmail

    # send message
    f = StringIO(msg.as_string())
    d = defer.Deferred()
    factory = ESMTPSenderFactory(username, password, fromEmail, toEmail, f, d,
                                 requireAuthentication=requireAuth,
                                 requireTransportSecurity=requireSSL)
    if senderDomainName is not None:
        factory.domain = senderDomainName
    connectTCP(smtphost, port, factory)

    return d
コード例 #9
0
def compose_plain_email(fromEmail, toEmail, content, headers):
    msg = MIMEText(content)

    # Setup the mail headers
    for (header, value) in headers.items():
        msg[header] = value

    msg.set_charset('utf-8')

    headkeys = [k.lower() for k in headers.keys()]

    # Add required headers if not present
    if "message-id" not in headkeys:
        msg["Message-ID"] = messageid()
    if "date" not in headkeys:
        msg["Date"] = rfc822date()
    if "from" not in headkeys and "sender" not in headkeys:
        msg["From"] = fromEmail
    if "to" not in headkeys and "cc" not in headkeys and "bcc" not in headkeys:
        msg["To"] = toEmail
    if "reply-to" not in headkeys:
        msg["Reply-To"] = SUPPORT_ADDRESS
    if "user-agent" not in headkeys:
        msg["User-Agent"] = USER_AGENT

    return msg.as_string()
コード例 #10
0
ファイル: signup.py プロジェクト: fusionapp/mantissa
    def sendEmail(self, url, attempt, email, _sendEmail=_sendEmail):
        """
        Send an email for the given L{_PasswordResetAttempt}.

        @type url: L{URL}
        @param url: The URL of the password reset page.

        @type attempt: L{_PasswordResetAttempt}
        @param attempt: An L{Item} representing a particular user's attempt to
        reset their password.

        @type email: C{str}
        @param email: The email will be sent to this address.
        """

        host = url.netloc.split(':', 1)[0]
        from_ = 'reset@' + host

        body = file(sibpath(__file__, 'reset.rfc2822')).read()
        body %= {'from': from_,
                 'to': email,
                 'date': rfc822.formatdate(),
                 'message-id': smtp.messageid(),
                 'link': url.child(attempt.key)}

        _sendEmail(from_, email, body)
コード例 #11
0
 def insertResentHeaders(i):
     m._headers.insert(i, ('resent-from', MH.Header(
         fromAddress.address).encode()))
     m._headers.insert(i, ('resent-to', MH.Header(
         mimeutil.flattenEmailAddresses(toAddresses)).encode()))
     m._headers.insert(i, ('resent-date', EU.formatdate()))
     m._headers.insert(i, ('resent-message-id',
                           smtp.messageid('divmod.xquotient')))
コード例 #12
0
ファイル: gateway.py プロジェクト: laborautonomo/leap_mail
    def _fix_headers(self, origmsg, newmsg, signkey):
        """
        Move some headers from C{origmsg} to C{newmsg}, delete unwanted
        headers from C{origmsg} and add new headers to C{newms}.

        Outgoing messages are either encrypted and signed or just signed
        before being sent. Because of that, they are packed inside new
        messages and some manipulation has to be made on their headers.

        Allowed headers for passing through:

            - From
            - Date
            - To
            - Subject
            - Reply-To
            - References
            - In-Reply-To
            - Cc

        Headers to be added:

            - Message-ID (i.e. should not use origmsg's Message-Id)
            - Received (this is added automatically by twisted smtp API)
            - OpenPGP (see #4447)

        Headers to be deleted:

            - User-Agent

        :param origmsg: The original message.
        :type origmsg: email.message.Message
        :param newmsg: The new message being created.
        :type newmsg: email.message.Message
        :param signkey: The key used to sign C{newmsg}
        :type signkey: OpenPGPKey
        """
        # move headers from origmsg to newmsg
        headers = origmsg.items()
        passthrough = [
            'from', 'date', 'to', 'subject', 'reply-to', 'references',
            'in-reply-to', 'cc'
        ]
        headers = filter(lambda x: x[0].lower() in passthrough, headers)
        for hkey, hval in headers:
            newmsg.add_header(hkey, hval)
            del(origmsg[hkey])
        # add a new message-id to newmsg
        newmsg.add_header('Message-Id', smtp.messageid())
        # add openpgp header to newmsg
        username, domain = signkey.address.split('@')
        newmsg.add_header(
            'OpenPGP', 'id=%s' % signkey.key_id,
            url='https://%s/key/%s' % (domain, username),
            preference='signencrypt')
        # delete user-agent from origmsg
        del(origmsg['user-agent'])
コード例 #13
0
    def _fix_headers(self, origmsg, newmsg, signkey):
        """
        Move some headers from C{origmsg} to C{newmsg}, delete unwanted
        headers from C{origmsg} and add new headers to C{newms}.

        Outgoing messages are either encrypted and signed or just signed
        before being sent. Because of that, they are packed inside new
        messages and some manipulation has to be made on their headers.

        Allowed headers for passing through:

            - From
            - Date
            - To
            - Subject
            - Reply-To
            - References
            - In-Reply-To
            - Cc

        Headers to be added:

            - Message-ID (i.e. should not use origmsg's Message-Id)
            - Received (this is added automatically by twisted smtp API)
            - OpenPGP (see #4447)

        Headers to be deleted:

            - User-Agent

        :param origmsg: The original message.
        :type origmsg: email.message.Message
        :param newmsg: The new message being created.
        :type newmsg: email.message.Message
        :param signkey: The key used to sign C{newmsg}
        :type signkey: OpenPGPKey
        """
        # move headers from origmsg to newmsg
        headers = origmsg.items()
        passthrough = [
            'from', 'date', 'to', 'subject', 'reply-to', 'references',
            'in-reply-to', 'cc'
        ]
        headers = filter(lambda x: x[0].lower() in passthrough, headers)
        for hkey, hval in headers:
            newmsg.add_header(hkey, hval)
            del (origmsg[hkey])
        # add a new message-id to newmsg
        newmsg.add_header('Message-Id', smtp.messageid())
        # add openpgp header to newmsg
        username, domain = signkey.address.split('@')
        newmsg.add_header('OpenPGP',
                          'id=%s' % signkey.key_id,
                          url='https://%s/key/%s' % (domain, username),
                          preference='signencrypt')
        # delete user-agent from origmsg
        del (origmsg['user-agent'])
コード例 #14
0
    def issueViaEmail(self,
                      issuer,
                      email,
                      product,
                      templateData,
                      domainName,
                      httpPort=80):
        """
        Send a ticket via email to the supplied address, which, when claimed, will
        create an avatar and allow the given product to endow it with
        things.

        @param issuer: An object, preferably a user, to track who issued this
        ticket.

        @param email: a str, formatted as an rfc2821 email address
        (user@domain) -- source routes not allowed.

        @param product: an instance of L{Product}

        @param domainName: a domain name, used as the domain part of the
        sender's address, and as the web server to generate a link to within
        the email.

        @param httpPort: a port number for the web server running on domainName

        @param templateData: A string containing an rfc2822-format email
        message, which will have several python values interpolated into it
        dictwise:

            %(from)s: To be used for the From: header; will contain an
             rfc2822-format address.

            %(to)s: the address that we are going to send to.

            %(date)s: an rfc2822-format date.

            %(message-id)s: an rfc2822 message-id

            %(link)s: an HTTP URL that we are generating a link to.

        """

        ticket = self.createTicket(issuer, unicode(email, 'ascii'), product)
        nonce = ticket.nonce

        signupInfo = {
            'from': 'signup@' + domainName,
            'to': email,
            'date': rfc822.formatdate(),
            'message-id': smtp.messageid(),
            'link': self.ticketLink(domainName, httpPort, nonce)
        }

        msg = templateData % signupInfo

        return ticket, _sendEmail(signupInfo['from'], email, msg)
コード例 #15
0
    def betterMessageID():
        """
        Strip out the domain in the default Twisted Message-ID value and replace with our configured
        server host name. That will avoid leaking internal app-server host names in a multi-host setup.

        @return: our safe message-id value
        @rtype: L{str}
        """
        return "{}@{}>".format(messageid().split("@")[0], config.ServerHostName)
コード例 #16
0
ファイル: bounce.py プロジェクト: BarnetteME1/indeed_scraper
def generateBounce(message, failedFrom, failedTo, transcript=''):
    """
    Generate a bounce message for an undeliverable email message.

    @type message: L{bytes}
    @param message: The undeliverable message.

    @type failedFrom: L{bytes}
    @param failedFrom: The originator of the undeliverable message.

    @type failedTo: L{bytes}
    @param failedTo: The destination of the undeliverable message.

    @type transcript: L{bytes}
    @param transcript: An error message to include in the bounce message.

    @rtype: 3-L{tuple} of (E{1}) L{bytes}, (E{2}) L{bytes}, (E{3}) L{bytes}
    @return: The originator, the destination and the contents of the bounce
        message.  The destination of the bounce message is the originator of
        the undeliverable message.
    """
    if not transcript:
        transcript = '''\
I'm sorry, the following address has permanent errors: %(failedTo)s.
I've given up, and I will not retry the message again.
''' % {
            'failedTo': failedTo
        }

    failedAddress = rfc822.AddressList(failedTo)[0][1]
    data = {
        'boundary': "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX'),
        'ctime': time.ctime(time.time()),
        'failedAddress': failedAddress,
        'failedDomain': failedAddress.split('@', 1)[1],
        'failedFrom': failedFrom,
        'failedTo': failedTo,
        'messageID': smtp.messageid(uniq='bounce'),
        'message': message,
        'transcript': transcript,
    }

    fp = StringIO.StringIO()
    fp.write(BOUNCE_FORMAT % data)
    orig = message.tell()
    message.seek(2, 0)
    sz = message.tell()
    message.seek(0, orig)
    if sz > 10000:
        while 1:
            line = message.readline()
            if len(line) <= 1:
                break
            fp.write(line)
    else:
        fp.write(message.read())
    return '', failedFrom, fp.getvalue()
コード例 #17
0
ファイル: smtpsender.py プロジェクト: gingerkaan/serveros
    def betterMessageID():
        """
        Strip out the domain in the default Twisted Message-ID value and replace with our configured
        server host name. That will avoid leaking internal app-server host names in a multi-host setup.

        @return: our safe message-id value
        @rtype: L{str}
        """
        return "{}@{}>".format(messageid().split("@")[0],
                               config.ServerHostName)
コード例 #18
0
ファイル: bounce.py プロジェクト: 12019/OpenWrt_Luci_Lua
def generateBounce(message, failedFrom, failedTo, transcript=''):
    """
    Generate a bounce message for an undeliverable email message.

    @type message: L{bytes}
    @param message: The undeliverable message.

    @type failedFrom: L{bytes}
    @param failedFrom: The originator of the undeliverable message.

    @type failedTo: L{bytes}
    @param failedTo: The destination of the undeliverable message.

    @type transcript: L{bytes}
    @param transcript: An error message to include in the bounce message.

    @rtype: 3-L{tuple} of (E{1}) L{bytes}, (E{2}) L{bytes}, (E{3}) L{bytes}
    @return: The originator, the destination and the contents of the bounce
        message.  The destination of the bounce message is the originator of
        the undeliverable message.
    """
    if not transcript:
        transcript = '''\
I'm sorry, the following address has permanent errors: %(failedTo)s.
I've given up, and I will not retry the message again.
''' % {'failedTo': failedTo}

    failedAddress = rfc822.AddressList(failedTo)[0][1]
    data = {
        'boundary': "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX'),
        'ctime': time.ctime(time.time()),
        'failedAddress': failedAddress,
        'failedDomain': failedAddress.split('@', 1)[1],
        'failedFrom': failedFrom,
        'failedTo': failedTo,
        'messageID': smtp.messageid(uniq='bounce'),
        'message': message,
        'transcript': transcript,
        }

    fp = StringIO.StringIO()
    fp.write(BOUNCE_FORMAT % data)
    orig = message.tell()
    message.seek(2, 0)
    sz = message.tell()
    message.seek(0, orig)
    if sz > 10000:
        while 1:
            line = message.readline()
            if len(line)<=1:
                break
            fp.write(line)
    else:
        fp.write(message.read())
    return '', failedFrom, fp.getvalue()
コード例 #19
0
    def writeHeaders(self,
                     fromAddress,
                     toAddress,
                     subject=None,
                     inReplyTo=None,
                     includeMessageID=True,
                     contentType='text/plain; charset="utf-8"',
                     **kwargs):
        """Write all headers into the response email.

        :param str fromAddress: The email address for the ``'From:'`` header.
        :param str toAddress: The email address for the ``'To:'`` header.
        :type subject: ``None`` or :any:`str`
        :param subject: The ``'Subject:'`` header.
        :type inReplyTo: ``None`` or :any:`str`
        :param inReplyTo: If set, an ``'In-Reply-To:'`` header will be
            generated. This should be set to the ``'Message-ID:'`` header from
            the client's original request email.
        :param bool includeMessageID: If ``True``, generate and include a
            ``'Message-ID:'`` header for the response.
        :param str contentType: The ``'Content-Type:'`` header.
        :kwargs: If given, the key will become the name of the header, and the
            value will become the contents of that header.
        """

        fromAddress = fromAddress.decode('utf-8') if isinstance(
            fromAddress, bytes) else fromAddress
        toAddress = toAddress.decode('utf-8') if isinstance(
            toAddress, bytes) else toAddress

        self.write("From: %s" % fromAddress)
        self.write("To: %s" % toAddress)
        if includeMessageID:
            self.write("Message-ID: %s" % smtp.messageid())
        if inReplyTo:
            self.write("In-Reply-To: %s" % inReplyTo)
        self.write("Content-Type: %s" % contentType)
        self.write("Date: %s" % smtp.rfc822date().decode('utf-8'))

        if not subject:
            subject = '[no subject]'
        if not subject.lower().startswith('re'):
            subject = "Re: " + subject
        self.write("Subject: %s" % subject)

        if kwargs:
            for headerName, headerValue in kwargs.items():
                headerName = headerName.capitalize()
                headerName = headerName.replace(' ', '-')
                headerName = headerName.replace('_', '-')
                header = "%s: %s" % (headerName, headerValue)
                self.write(header)

        # The first blank line designates that the headers have ended:
        self.write(self.delimiter)
コード例 #20
0
ファイル: signup.py プロジェクト: fusionapp/mantissa
    def issueViaEmail(self, issuer, email, product, templateData,
                      domainName, httpPort=80):
        """
        Send a ticket via email to the supplied address, which, when claimed, will
        create an avatar and allow the given product to endow it with
        things.

        @param issuer: An object, preferably a user, to track who issued this
        ticket.

        @param email: a str, formatted as an rfc2821 email address
        (user@domain) -- source routes not allowed.

        @param product: an instance of L{Product}

        @param domainName: a domain name, used as the domain part of the
        sender's address, and as the web server to generate a link to within
        the email.

        @param httpPort: a port number for the web server running on domainName

        @param templateData: A string containing an rfc2822-format email
        message, which will have several python values interpolated into it
        dictwise:

            %(from)s: To be used for the From: header; will contain an
             rfc2822-format address.

            %(to)s: the address that we are going to send to.

            %(date)s: an rfc2822-format date.

            %(message-id)s: an rfc2822 message-id

            %(link)s: an HTTP URL that we are generating a link to.

        """

        ticket = self.createTicket(issuer,
                                   unicode(email, 'ascii'),
                                   product)
        nonce = ticket.nonce

        signupInfo = {'from': 'signup@'+domainName,
                      'to': email,
                      'date': rfc822.formatdate(),
                      'message-id': smtp.messageid(),
                      'link': self.ticketLink(domainName, httpPort, nonce)}

        msg = templateData % signupInfo

        return ticket, _sendEmail(signupInfo['from'], email, msg)
コード例 #21
0
def send_plain_email(fromEmail, toEmail, content, headers):
    if REQUIRE_AUTH:
        password = FilePath(SMTP_PASSWORD_PATH).getContent().strip()
    else:
        password = None

    msg = MIMEText(content)

    # Setup the mail headers
    for (header, value) in headers.items():
        msg[header] = value

    headkeys = [k.lower() for k in headers.keys()]

    # Add required headers if not present
    if "message-id" not in headkeys:
        msg["Message-ID"] = messageid()
    if "date" not in headkeys:
        msg["Date"] = rfc822date()
    if "from" not in headkeys and "sender" not in headkeys:
        msg["From"] = fromEmail
    if "to" not in headkeys and "cc" not in headkeys and "bcc" not in headkeys:
        msg["To"] = toEmail
    if "reply-to" not in headkeys:
        msg["Reply-To"] = SUPPORT_ADDRESS
    if "user-agent" not in headkeys:
        msg["User-Agent"] = USER_AGENT
    if "content-type" not in headkeys:
        msg["Content-Type"] = CONTENT_TYPE

    # send message
    f = StringIO(msg.as_string())
    d = defer.Deferred()
    factory = ESMTPSenderFactory(
        SMTP_USERNAME,
        password,
        fromEmail,
        toEmail,
        f,
        d,
        requireAuthentication=REQUIRE_AUTH,
        requireTransportSecurity=REQUIRE_TRANSPORT_SECURITY)
    factory.domain = SENDER_DOMAIN
    connectTCP(SMTP_HOST, SMTP_PORT, factory)

    return d
コード例 #22
0
    def writeHeaders(self, fromAddress, toAddress, subject=None,
                     inReplyTo=None, includeMessageID=True,
                     contentType='text/plain; charset="utf-8"', **kwargs):
        """Write all headers into the response email.

        :param str fromAddress: The email address for the ``'From:'`` header.
        :param str toAddress: The email address for the ``'To:'`` header.
        :type subject: None or str
        :param subject: The ``'Subject:'`` header.
        :type inReplyTo: None or str
        :param inReplyTo: If set, an ``'In-Reply-To:'`` header will be
            generated. This should be set to the ``'Message-ID:'`` header from
            the client's original request email.
        :param bool includeMessageID: If ``True``, generate and include a
            ``'Message-ID:'`` header for the response.
        :param str contentType: The ``'Content-Type:'`` header.
        :kwargs: If given, the key will become the name of the header, and the
            value will become the Contents of that header.
        """
        self.write("From: %s" % fromAddress)
        self.write("To: %s" % toAddress)
        if includeMessageID:
            self.write("Message-ID: %s" % smtp.messageid().encode('utf-8'))
        if inReplyTo:
            self.write("In-Reply-To: %s" % inReplyTo.encode('utf-8'))
        self.write("Content-Type: %s" % contentType.encode('utf-8'))
        self.write("Date: %s" % smtp.rfc822date().encode('utf-8'))

        if not subject:
            subject = '[no subject]'
        if not subject.lower().startswith('re'):
            subject = "Re: " + subject
        self.write("Subject: %s" % subject.encode('utf-8'))

        if kwargs:
            for headerName, headerValue in kwargs.items():
                headerName = headerName.capitalize()
                headerName = headerName.replace(' ', '-')
                headerName = headerName.replace('_', '-')
                header = "%s: %s" % (headerName, headerValue)
                self.write(header.encode('utf-8'))

        # The first blank line designates that the headers have ended:
        self.write(self.delimiter)
コード例 #23
0
    def notifyModerators(self, moderators, article):
        """
        Send an article to a list of group moderators to be moderated.

        @param moderators: A C{list} of C{str} giving RFC 2821 addresses of
            group moderators to notify.

        @param article: The article requiring moderation.
        @type article: L{Article}

        @return: A L{Deferred} which fires with the result of sending the email.
        """
        # Moderated postings go through as long as they have an Approved
        # header, regardless of what the value is
        group = article.getHeader('Newsgroups')
        subject = article.getHeader('Subject')

        if self._sender is None:
            # This case should really go away.  This isn't a good default.
            sender = 'twisted-news@' + socket.gethostname()
        else:
            sender = self._sender

        msg = Message()
        msg['Message-ID'] = smtp.messageid()
        msg['From'] = sender
        msg['To'] = ', '.join(moderators)
        msg['Subject'] = 'Moderate new %s message: %s' % (group, subject)
        msg['Content-Type'] = 'message/rfc822'

        payload = Message()
        for header, value in article.headers.values():
            payload.add_header(header, value)
        payload.set_payload(article.body)

        msg.attach(payload)

        out = StringIO.StringIO()
        gen = Generator(out, False)
        gen.flatten(msg)
        msg = out.getvalue()

        return self.sendmail(self._mailhost, sender, moderators, msg)
コード例 #24
0
    def notifyModerators(self, moderators, article):
        """
        Send an article to a list of group moderators to be moderated.

        @param moderators: A C{list} of C{str} giving RFC 2821 addresses of
            group moderators to notify.

        @param article: The article requiring moderation.
        @type article: L{Article}

        @return: A L{Deferred} which fires with the result of sending the email.
        """
        # Moderated postings go through as long as they have an Approved
        # header, regardless of what the value is
        group = article.getHeader('Newsgroups')
        subject = article.getHeader('Subject')

        if self._sender is None:
            # This case should really go away.  This isn't a good default.
            sender = 'twisted-news@' + socket.gethostname()
        else:
            sender = self._sender

        msg = Message()
        msg['Message-ID'] = smtp.messageid()
        msg['From'] = sender
        msg['To'] = ', '.join(moderators)
        msg['Subject'] = 'Moderate new %s message: %s' % (group, subject)
        msg['Content-Type'] = 'message/rfc822'

        payload = Message()
        for header, value in article.headers.values():
            payload.add_header(header, value)
        payload.set_payload(article.body)

        msg.attach(payload)

        out = StringIO.StringIO()
        gen = Generator(out, False)
        gen.flatten(msg)
        msg = out.getvalue()

        return self.sendmail(self._mailhost, sender, moderators, msg)
コード例 #25
0
def send_plain_email(fromEmail, toEmail, content, headers):
    if REQUIRE_AUTH:
        password = FilePath(SMTP_PASSWORD_PATH).getContent().strip()
    else:
        password = None

    msg = MIMEText(content)

    # Setup the mail headers
    for (header, value) in headers.items():
        msg[header] = value

    headkeys = [k.lower() for k in headers.keys()]

    # Add required headers if not present
    if "message-id" not in headkeys:
        msg["Message-ID"] = messageid()
    if "date" not in headkeys:
        msg["Date"] = rfc822date()
    if "from" not in headkeys and "sender" not in headkeys:
        msg["From"] = fromEmail
    if "to" not in headkeys and "cc" not in headkeys and "bcc" not in headkeys:
        msg["To"] = toEmail
    if "reply-to" not in headkeys:
        msg["Reply-To"] = SUPPORT_ADDRESS
    if "user-agent" not in headkeys:
        msg["User-Agent"] = USER_AGENT
    if "content-type" not in headkeys:
        msg["Content-Type"] = CONTENT_TYPE

    # send message
    f = StringIO(msg.as_string())
    d = defer.Deferred()
    factory = ESMTPSenderFactory(SMTP_USERNAME, password, fromEmail, toEmail, f, d,
                                 requireAuthentication=REQUIRE_AUTH,
                                 requireTransportSecurity=REQUIRE_TRANSPORT_SECURITY)
    factory.domain = SENDER_DOMAIN
    connectTCP(SMTP_HOST, SMTP_PORT, factory)

    return d
コード例 #26
0
    def test_postApproved(self):
        """
        L{INewsStorage.postRequest} posts the message if it includes an
        I{Approved} header.
        """
        group = "example.group"
        moderator = "*****@*****.**"
        mailhost = "127.0.0.1"
        sender = "*****@*****.**"
        articleID = messageid()
        storage = self.getStorage(
            [group], {group: [moderator]}, mailhost, sender)
        message = self.getApprovedMessage(articleID, group)
        result = storage.postRequest(message)

        def cbPosted(ignored):
            self.assertEqual(self._email, [])
            exists = storage.articleExistsRequest(articleID)
            exists.addCallback(self.assertTrue)
            return exists
        result.addCallback(cbPosted)
        return result
コード例 #27
0
    def test_postApproved(self):
        """
        L{INewsStorage.postRequest} posts the message if it includes an
        I{Approved} header.
        """
        group = "example.group"
        moderator = "*****@*****.**"
        mailhost = "127.0.0.1"
        sender = "*****@*****.**"
        articleID = messageid()
        storage = self.getStorage(
            [group], {group: [moderator]}, mailhost, sender)
        message = self.getApprovedMessage(articleID, group)
        result = storage.postRequest(message)

        def cbPosted(ignored):
            self.assertEquals(self._email, [])
            exists = storage.articleExistsRequest(articleID)
            exists.addCallback(self.assertTrue)
            return exists
        result.addCallback(cbPosted)
        return result
コード例 #28
0
def MIME_mail_build(src_name, src_mail, dest_name, dest_mail, mail_subject,
                    mail_body):
    """
    Prepare the mail headers

    :param src_name: A source name
    :param src_mail: A source email adddress
    :param dest_name: A destination name
    :param dest_mail: A destination email address
    :param mail_subject: A mail subject
    :param mail_body: A mail body
    :return: A mail headers
    """
    multipart = MIMEMultipart('alternative')
    multipart['Message-Id'] = Header(messageid())
    multipart['Subject'] = Header(mail_subject.encode(), 'UTF-8').encode()
    multipart['Date'] = utils.formatdate()
    multipart['To'] = Header(dest_name.encode(),
                             'UTF-8').encode() + " <" + dest_mail + ">"
    multipart['From'] = Header(src_name.encode(),
                               'UTF-8').encode() + " <" + src_mail + ">"
    multipart['X-Mailer'] = "fnord"
    multipart.attach(MIMEText(mail_body.encode(), 'plain', 'UTF-8'))
    return BytesIO(multipart.as_bytes())  # pylint: disable=no-member
コード例 #29
0
ファイル: emailsend.py プロジェクト: nakedible/vpnease-l2tp
def _format_smtp_message(from_addr, to_addr, subject, content, attachments):
    #
    #  The central problem in formatting SMTP messages correctly is to support
    #  non-US-ASCII characters correctly.  The solution here simply mimics what
    #  Thunderbird (Icedove) does when sending a message containing both ISO 8859-1
    #  and full Unicode characters.
    #
    #  The solution for headers and content is unfortunately different.
    #
    #
    #  ESMTP:
    #    * http://www.ietf.org/rfc/rfc2821.txt
    #    * http://www.ietf.org/rfc/rfc2822.txt
    #
    #  MIME (cannot send other an US-ASCII with 2822 format):
    #    * http://en.wikipedia.org/wiki/MIME
    #    * http://en.wikipedia.org/wiki/Unicode_and_e-mail
    #    * http://www.ietf.org/rfc/rfc2045.txt
    #    * http://www.ietf.org/rfc/rfc2046.txt
    #    * http://www.ietf.org/rfc/rfc2048.txt
    #    * http://www.ietf.org/rfc/rfc2049.txt
    #    * http://www.ietf.org/rfc/rfc2183.txt
    
    # FIXME: some problems with mixed UTF vs. non-UTF characters in header.
    # escape all?
    def _hdrescape(x):
        res = ''
        for i in xrange(len(x)):
            c = x[i]
            ci = ord(c)
            if (ci in [0x0a, 0x0d]):
                # suppress in headers
                pass
            elif (ci >= 0x20 and ci <= 0x7e):
                res += c
            else:
                # See: http://en.wikipedia.org/wiki/MIME#Encoded-Word
                utf = c.encode('utf-8')
                res += '=?UTF-8?Q?'
                for j in xrange(len(utf)):
                    res += '=%02X' % ord(utf[j])
                res += '?='
        return res

    def _bodyescape(x):
        res = ''
        for i in xrange(len(x)):
            c = x[i]
            ci = ord(c)
            if (ci >= 0x20 and ci <= 0x7e) or (ci in [0x0a, 0x0d]):
                res += c
            else:
                utf = c.encode('utf-8')
                for j in xrange(len(utf)):
                    res += '=%02X' % ord(utf[j])
        return res

    # Check sanity
    #
    # No checks are done for attachments at the moment.  The attachments are I/O objects
    # so we don't know their size off hand.  However, below, when we're creating a MIME
    # message, we'll limit the attachment size.
    if (len(from_addr) > STRING_SANITY) or \
       (len(to_addr) > STRING_SANITY) or \
       (len(subject) > STRING_SANITY) or \
       (len(content) > MAX_CONTENT_SIZE):
        raise Exception('parameter sanity check failed')

    # Escape from and to addresses - XXX: no support for non-ascii email addresses right now
    from_addr = from_addr.encode('ascii')
    to_addr = to_addr.encode('ascii')

    # Start building MIME message into a string I/O object
    fp = cStringIO.StringIO()
    mw = MimeWriter.MimeWriter(fp)
    mw.addheader('From', _hdrescape(from_addr))
    mw.addheader('To', _hdrescape(to_addr))
    mw.addheader('Subject', _hdrescape(subject))
    mw.addheader('Date', smtp.rfc822date())
    mw.addheader('Message-ID', smtp.messageid())
    mw.addheader('MIME-Version', '1.0')

    # Add body and possibly attachments
    if len(attachments.keys()) == 0:
        # XXX: using 'plist' of startbody() results in multiline encoding, which I dislike
        mw.addheader('Content-Transfer-Encoding', 'quoted-printable')
        mw.addheader('Content-Disposition', 'inline')
        mw.flushheaders()
        f = mw.startbody('text/plain; charset=UTF-8')   # XXX: format=flowed?
        f.write(_bodyescape(content))
    else:
        mw.flushheaders()
        mw.startmultipartbody('mixed')

        f = mw.nextpart()
        f.addheader('Content-Disposition', 'inline')
        f.addheader('Content-Transfer-Encoding', 'quoted-printable')
        f.flushheaders()
        f2 = f.startbody('text/plain; charset=UTF-8')   # XXX: format=flowed?
        f2.write(_bodyescape(content))

        for i in attachments.keys():
            f = mw.nextpart()
            f.addheader('Content-Disposition', 'inline; filename=%s' % i)  # FIXME: filter / escape filenames
            f.addheader('Content-Transfer-Encoding', 'base64')
            f.flushheaders()

            mimetype, encoding = mimetypes.guess_type(i)
            f2 = f.startbody(mimetype)
            fdata = attachments[i]

            # We read the attachment into a variable here for base64 encoding (and
            # size check), this may take several megabytes of temporary memory.
            t = fdata.read(MAX_ATTACHMENT_SIZE + 1)
            if len(t) > MAX_ATTACHMENT_SIZE:
                raise Exception('attachment too long')
            f2.write(t.encode('base64'))

        mw.lastpart()
        
    # Done, convert to string and we're done
    return fp.getvalue()
コード例 #30
0
    def _fix_headers(self, msg, newmsg, sign_address):
        """
        Move some headers from C{origmsg} to C{newmsg}, delete unwanted
        headers from C{origmsg} and add new headers to C{newms}.

        Outgoing messages are either encrypted and signed or just signed
        before being sent. Because of that, they are packed inside new
        messages and some manipulation has to be made on their headers.

        Allowed headers for passing through:

            - From
            - Date
            - To
            - Subject
            - Reply-To
            - References
            - In-Reply-To
            - Cc

        Headers to be added:

            - Message-ID (i.e. should not use origmsg's Message-Id)
            - Received (this is added automatically by twisted smtp API)
            - OpenPGP (see #4447)

        Headers to be deleted:

            - User-Agent

        :param msg: The original message.
        :type msg: email.message.Message
        :param newmsg: The new message being created.
        :type newmsg: email.message.Message
        :param sign_address: The address used to sign C{newmsg}
        :type sign_address: str

        :return: A Deferred with a touple:
                 (new Message with the unencrypted headers,
                  original Message with headers removed)
        :rtype: Deferred
        """
        origmsg = deepcopy(msg)
        # move headers from origmsg to newmsg
        headers = origmsg.items()
        passthrough = [
            'from', 'date', 'to', 'subject', 'reply-to', 'references',
            'in-reply-to', 'cc'
        ]
        headers = filter(lambda x: x[0].lower() in passthrough, headers)
        for hkey, hval in headers:
            newmsg.add_header(hkey, hval)
            del (origmsg[hkey])
        # add a new message-id to newmsg
        newmsg.add_header('Message-Id', smtp.messageid())
        # delete user-agent from origmsg
        del (origmsg['user-agent'])

        def add_openpgp_header(signkey):
            username, domain = sign_address.split('@')
            newmsg.add_header(
                'OpenPGP', 'id=%s' % signkey.fingerprint,
                url='https://%s/key/%s' % (domain, username),
                preference='signencrypt')
            return newmsg, origmsg

        d = self._keymanager.get_key(sign_address, private=True)
        d.addCallback(add_openpgp_header)
        return d
コード例 #31
0
ファイル: inbound.py プロジェクト: gingerkaan/serveros
    def processReply(self, msg):
        # extract the token from the To header
        _ignore_name, addr = email.utils.parseaddr(msg['To'])
        if addr:
            # addr looks like: [email protected]
            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(label="MailReceiver.processReply")
        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:x-uid:"):
                uid = organizer[10:]
                record = yield self.directory.recordWithUID(uid)
                try:
                    if record and record.emailAddresses:
                        toAddr = list(record.emailAddresses)[0]
                except AttributeError:
                    pass

            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.as_string())
            returnValue(self.REPLY_FORWARDED_TO_ORGANIZER)

        # Process the imip attachment; inject to calendar server

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

        # Don't let a missing PRODID prevent the reply from being processed
        if not calendar.hasProperty("PRODID"):
            calendar.addProperty(Property("PRODID", "Unknown"))

        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(label="MailReceiver.processReply")
        yield txn.enqueue(IMIPReplyWork,
                          organizer=organizer,
                          attendee=attendee,
                          icalendarText=str(calendar))
        yield txn.commit()
        returnValue(self.INJECTION_SUBMITTED)
コード例 #32
0
    def processReply(self, msg):
        # extract the token from the To header
        _ignore_name, addr = email.utils.parseaddr(msg['To'])
        if addr:
            # addr looks like: [email protected]
            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, 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)
コード例 #33
0
ファイル: emailsend.py プロジェクト: zeus911/vpnease-l2tp
def _format_smtp_message(from_addr, to_addr, subject, content, attachments):
    #
    #  The central problem in formatting SMTP messages correctly is to support
    #  non-US-ASCII characters correctly.  The solution here simply mimics what
    #  Thunderbird (Icedove) does when sending a message containing both ISO 8859-1
    #  and full Unicode characters.
    #
    #  The solution for headers and content is unfortunately different.
    #
    #
    #  ESMTP:
    #    * http://www.ietf.org/rfc/rfc2821.txt
    #    * http://www.ietf.org/rfc/rfc2822.txt
    #
    #  MIME (cannot send other an US-ASCII with 2822 format):
    #    * http://en.wikipedia.org/wiki/MIME
    #    * http://en.wikipedia.org/wiki/Unicode_and_e-mail
    #    * http://www.ietf.org/rfc/rfc2045.txt
    #    * http://www.ietf.org/rfc/rfc2046.txt
    #    * http://www.ietf.org/rfc/rfc2048.txt
    #    * http://www.ietf.org/rfc/rfc2049.txt
    #    * http://www.ietf.org/rfc/rfc2183.txt

    # FIXME: some problems with mixed UTF vs. non-UTF characters in header.
    # escape all?
    def _hdrescape(x):
        res = ''
        for i in xrange(len(x)):
            c = x[i]
            ci = ord(c)
            if (ci in [0x0a, 0x0d]):
                # suppress in headers
                pass
            elif (ci >= 0x20 and ci <= 0x7e):
                res += c
            else:
                # See: http://en.wikipedia.org/wiki/MIME#Encoded-Word
                utf = c.encode('utf-8')
                res += '=?UTF-8?Q?'
                for j in xrange(len(utf)):
                    res += '=%02X' % ord(utf[j])
                res += '?='
        return res

    def _bodyescape(x):
        res = ''
        for i in xrange(len(x)):
            c = x[i]
            ci = ord(c)
            if (ci >= 0x20 and ci <= 0x7e) or (ci in [0x0a, 0x0d]):
                res += c
            else:
                utf = c.encode('utf-8')
                for j in xrange(len(utf)):
                    res += '=%02X' % ord(utf[j])
        return res

    # Check sanity
    #
    # No checks are done for attachments at the moment.  The attachments are I/O objects
    # so we don't know their size off hand.  However, below, when we're creating a MIME
    # message, we'll limit the attachment size.
    if (len(from_addr) > STRING_SANITY) or \
       (len(to_addr) > STRING_SANITY) or \
       (len(subject) > STRING_SANITY) or \
       (len(content) > MAX_CONTENT_SIZE):
        raise Exception('parameter sanity check failed')

    # Escape from and to addresses - XXX: no support for non-ascii email addresses right now
    from_addr = from_addr.encode('ascii')
    to_addr = to_addr.encode('ascii')

    # Start building MIME message into a string I/O object
    fp = cStringIO.StringIO()
    mw = MimeWriter.MimeWriter(fp)
    mw.addheader('From', _hdrescape(from_addr))
    mw.addheader('To', _hdrescape(to_addr))
    mw.addheader('Subject', _hdrescape(subject))
    mw.addheader('Date', smtp.rfc822date())
    mw.addheader('Message-ID', smtp.messageid())
    mw.addheader('MIME-Version', '1.0')

    # Add body and possibly attachments
    if len(attachments.keys()) == 0:
        # XXX: using 'plist' of startbody() results in multiline encoding, which I dislike
        mw.addheader('Content-Transfer-Encoding', 'quoted-printable')
        mw.addheader('Content-Disposition', 'inline')
        mw.flushheaders()
        f = mw.startbody('text/plain; charset=UTF-8')  # XXX: format=flowed?
        f.write(_bodyescape(content))
    else:
        mw.flushheaders()
        mw.startmultipartbody('mixed')

        f = mw.nextpart()
        f.addheader('Content-Disposition', 'inline')
        f.addheader('Content-Transfer-Encoding', 'quoted-printable')
        f.flushheaders()
        f2 = f.startbody('text/plain; charset=UTF-8')  # XXX: format=flowed?
        f2.write(_bodyescape(content))

        for i in attachments.keys():
            f = mw.nextpart()
            f.addheader('Content-Disposition', 'inline; filename=%s' %
                        i)  # FIXME: filter / escape filenames
            f.addheader('Content-Transfer-Encoding', 'base64')
            f.flushheaders()

            mimetype, encoding = mimetypes.guess_type(i)
            f2 = f.startbody(mimetype)
            fdata = attachments[i]

            # We read the attachment into a variable here for base64 encoding (and
            # size check), this may take several megabytes of temporary memory.
            t = fdata.read(MAX_ATTACHMENT_SIZE + 1)
            if len(t) > MAX_ATTACHMENT_SIZE:
                raise Exception('attachment too long')
            f2.write(t.encode('base64'))

        mw.lastpart()

    # Done, convert to string and we're done
    return fp.getvalue()
コード例 #34
0
ファイル: bounce.py プロジェクト: ling-1/GETAiqiyiDanmu
def generateBounce(message, failedFrom, failedTo, transcript="", encoding="utf-8"):
    """
    Generate a bounce message for an undeliverable email message.

    @type message: a file-like object
    @param message: The undeliverable message.

    @type failedFrom: L{bytes} or L{unicode}
    @param failedFrom: The originator of the undeliverable message.

    @type failedTo: L{bytes} or L{unicode}
    @param failedTo: The destination of the undeliverable message.

    @type transcript: L{bytes} or L{unicode}
    @param transcript: An error message to include in the bounce message.

    @type encoding: L{str} or L{unicode}
    @param encoding: Encoding to use, default: utf-8

    @rtype: 3-L{tuple} of (E{1}) L{bytes}, (E{2}) L{bytes}, (E{3}) L{bytes}
    @return: The originator, the destination and the contents of the bounce
        message.  The destination of the bounce message is the originator of
        the undeliverable message.
    """

    if isinstance(failedFrom, bytes):
        failedFrom = failedFrom.decode(encoding)

    if isinstance(failedTo, bytes):
        failedTo = failedTo.decode(encoding)

    if not transcript:
        transcript = """\
I'm sorry, the following address has permanent errors: {failedTo}.
I've given up, and I will not retry the message again.
""".format(
            failedTo=failedTo
        )

    failedAddress = email.utils.parseaddr(failedTo)[1]
    data = {
        "boundary": "{}_{}_{}".format(time.time(), os.getpid(), "XXXXX"),
        "ctime": time.ctime(time.time()),
        "failedAddress": failedAddress,
        "failedDomain": failedAddress.split("@", 1)[1],
        "failedFrom": failedFrom,
        "failedTo": failedTo,
        "messageID": smtp.messageid(uniq="bounce"),
        "message": message,
        "transcript": transcript,
    }

    fp = StringIO()
    fp.write(BOUNCE_FORMAT.format(**data))
    orig = message.tell()
    message.seek(0, SEEK_END)
    sz = message.tell()
    message.seek(orig, SEEK_SET)
    if sz > 10000:
        while 1:
            line = message.readline()
            if isinstance(line, bytes):
                line = line.decode(encoding)
            if len(line) <= 0:
                break
            fp.write(line)
    else:
        messageContent = message.read()
        if isinstance(messageContent, bytes):
            messageContent = messageContent.decode(encoding)
        fp.write(messageContent)
    return b"", failedFrom.encode(encoding), fp.getvalue().encode(encoding)
コード例 #35
0
ファイル: service.py プロジェクト: leapcode/leap_mail
    def _fix_headers(self, msg, newmsg, sign_address):
        """
        Move some headers from C{origmsg} to C{newmsg}, delete unwanted
        headers from C{origmsg} and add new headers to C{newms}.

        Outgoing messages are either encrypted and signed or just signed
        before being sent. Because of that, they are packed inside new
        messages and some manipulation has to be made on their headers.

        Allowed headers for passing through:

            - From
            - Date
            - To
            - Subject
            - Reply-To
            - References
            - In-Reply-To
            - Cc

        Headers to be added:

            - Message-ID (i.e. should not use origmsg's Message-Id)
            - Received (this is added automatically by twisted smtp API)
            - OpenPGP (see #4447)

        Headers to be deleted:

            - User-Agent

        :param msg: The original message.
        :type msg: email.message.Message
        :param newmsg: The new message being created.
        :type newmsg: email.message.Message
        :param sign_address: The address used to sign C{newmsg}
        :type sign_address: str

        :return: A Deferred with a touple:
                 (new Message with the unencrypted headers,
                  original Message with headers removed)
        :rtype: Deferred
        """
        origmsg = deepcopy(msg)
        # move headers from origmsg to newmsg
        headers = origmsg.items()
        passthrough = [
            'from', 'date', 'to', 'subject', 'reply-to', 'references',
            'in-reply-to', 'cc'
        ]
        headers = filter(lambda x: x[0].lower() in passthrough, headers)
        for hkey, hval in headers:
            newmsg.add_header(hkey, hval)
            del (origmsg[hkey])
        # add a new message-id to newmsg
        newmsg.add_header('Message-Id', smtp.messageid())
        # delete user-agent from origmsg
        del (origmsg['user-agent'])

        def add_openpgp_header(signkey):
            username, domain = sign_address.split('@')
            newmsg.add_header(
                'OpenPGP', 'id=%s' % signkey.fingerprint,
                url='https://%s/key/%s' % (domain, username),
                preference='signencrypt')
            return newmsg, origmsg

        d = self._keymanager.get_key(sign_address, OpenPGPKey, private=True)
        d.addCallback(add_openpgp_header)
        return d
コード例 #36
0
def createMessage(composer,
                  cabinet,
                  msgRepliedTo,
                  fromAddress,
                  toAddresses,
                  subject,
                  messageBody,
                  cc,
                  bcc,
                  files,
                  createMessageObject=None):
    """
    Create an outgoing message, format the body into MIME parts, and
    populate its headers.

    @param createMessageObject: A one-argument callable which will be
    invoked with a file-like object containing MIME text and which
    should return a Message instance associated with objects
    representing that MIME data.
    """
    MC.add_charset('utf-8', None, MC.QP, 'utf-8')

    encode = lambda s: MH.Header(s).encode()

    s = S.StringIO()
    wrappedMsgBody = FlowedParagraph.fromRFC2646(messageBody).asRFC2646()
    m = MT.MIMEText(wrappedMsgBody, 'plain', 'utf-8')
    m.set_param("format", "flowed")

    fileItems = []
    if files:
        attachmentParts = []
        for storeID in files:
            a = composer.store.getItemByID(long(storeID))
            if isinstance(a, Part):
                a = cabinet.createFileItem(
                    a.getParam('filename',
                               default=u'',
                               header=u'content-disposition'),
                    unicode(a.getContentType()), a.getBody(decode=True))
            fileItems.append(a)
            attachmentParts.append(_fileItemToEmailPart(a))

        m = MMP.MIMEMultipart('mixed', None, [m] + attachmentParts)

    m['From'] = encode(fromAddress.address)
    m['To'] = encode(mimeutil.flattenEmailAddresses(toAddresses))
    m['Subject'] = encode(subject)
    m['Date'] = EU.formatdate()
    m['Message-ID'] = smtp.messageid('divmod.xquotient')

    if cc:
        m['Cc'] = encode(mimeutil.flattenEmailAddresses(cc))
    if msgRepliedTo is not None:
        #our parser does not remove continuation whitespace, so to
        #avoid duplicating it --
        refs = [
            hdr.value for hdr in msgRepliedTo.impl.getHeaders("References")
        ]
        if len(refs) == 0:
            irt = [
                hdr.value
                for hdr in msgRepliedTo.impl.getHeaders("In-Reply-To")
            ]
            if len(irt) == 1:
                refs = irt
            else:
                refs = []
        msgids = msgRepliedTo.impl.getHeaders("Message-ID")
        for hdr in msgids:
            msgid = hdr.value
            refs.append(msgid)
            #As far as I can tell, the email package doesn't handle
            #multiple values for headers automatically, so here's some
            #continuation whitespace.
            m['References'] = u'\n\t'.join(refs)
            m['In-Reply-To'] = msgid
            break
    G.Generator(s).flatten(m)
    s.seek(0)

    if createMessageObject is None:

        def createMessageObject(messageFile):
            return composer.createMessageAndQueueIt(fromAddress.address,
                                                    messageFile, True)

    msg = createMessageObject(s)

    # there is probably a better way than this, but there
    # isn't a way to associate the same file item with multiple
    # messages anyway, so there isn't a need to reflect that here
    for fileItem in fileItems:
        fileItem.message = msg
    return msg
コード例 #37
0
ファイル: mail.py プロジェクト: svn2github/calendarserver-raw
    def generateEmail(self, inviteState, calendar, orgEmail, orgCN,
        attendees, fromAddress, replyToAddress, toAddress, language='en'):

        details = self.getEventDetails(calendar, language=language)
        canceled = (calendar.propertyValue("METHOD") == "CANCEL")
        iconPath = self.getIconPath(details, canceled, language=language)

        with translationTo(language):
            msg = MIMEMultipart()
            msg["From"] = fromAddress
            msg["Reply-To"] = replyToAddress
            msg["To"] = toAddress
            msg["Date"] = rfc822date()
            msgId = messageid()
            msg["Message-ID"] = msgId

            if canceled:
                formatString = _("Event canceled: %(summary)s")
            elif inviteState == "new":
                formatString = _("Event invitation: %(summary)s")
            elif inviteState == "update":
                formatString = _("Event update: %(summary)s")
            else:
                formatString = _("Event reply: %(summary)s")

            # The translations we get back from gettext are utf-8 encoded
            # strings, so convert to unicode
            formatString = formatString.decode("utf-8")

            details['subject'] = msg['Subject'] = formatString % {
                'summary' : details['summary']
            }

            msgAlt = MIMEMultipart("alternative")
            msg.attach(msgAlt)

            # Get localized labels
            if canceled:
                details['inviteLabel'] = _("Event Canceled")
            else:
                if inviteState == "new":
                    details['inviteLabel'] = _("Event Invitation")
                if inviteState == "update":
                    details['inviteLabel'] = _("Event Update")
                else:
                    details['inviteLabel'] = _("Event Reply")

            details['dateLabel'] = _("Date")
            details['timeLabel'] = _("Time")
            details['durationLabel'] = _("Duration")
            details['recurrenceLabel'] = _("Occurs")
            details['descLabel'] = _("Description")
            details['orgLabel'] = _("Organizer")
            details['attLabel'] = _("Attendees")
            details['locLabel'] = _("Location")


            plainAttendeeList = []
            for cn, mailto in attendees:
                if cn:
                    plainAttendeeList.append(cn if not mailto else
                        "%s <%s>" % (cn, mailto))
                elif mailto:
                    plainAttendeeList.append("<%s>" % (mailto,))

            details['plainAttendees'] = ", ".join(plainAttendeeList)

            details['plainOrganizer'] = (orgCN if not orgEmail else
                "%s <%s>" % (orgCN, orgEmail))

            # The translations we get back from gettext are utf-8 encoded
            # strings, so convert to unicode
            for key in details.keys():
                if isinstance(details[key], str):
                    details[key] = details[key].decode("utf-8")

            # plain text version
            if canceled:
                plainTemplate = u"""%(subject)s

%(orgLabel)s: %(plainOrganizer)s
%(dateLabel)s: %(dateInfo)s %(recurrenceInfo)s
%(timeLabel)s: %(timeInfo)s %(durationInfo)s
"""
            else:
                plainTemplate = u"""%(subject)s

%(orgLabel)s: %(plainOrganizer)s
%(locLabel)s: %(location)s
%(dateLabel)s: %(dateInfo)s %(recurrenceInfo)s
%(timeLabel)s: %(timeInfo)s %(durationInfo)s
%(descLabel)s: %(description)s
%(attLabel)s: %(plainAttendees)s
"""

            plainText = plainTemplate % details

            msgPlain = MIMEText(plainText.encode("UTF-8"), "plain", "UTF-8")
            msgAlt.attach(msgPlain)

            # html version
            msgHtmlRelated = MIMEMultipart("related", type="text/html")
            msgAlt.attach(msgHtmlRelated)


            htmlAttendees = []
            for cn, mailto in attendees:
                if mailto:
                    htmlAttendees.append('<a href="mailto:%s">%s</a>' %
                        (mailto, cn))
                else:
                    htmlAttendees.append(cn)

            details['htmlAttendees'] = ", ".join(htmlAttendees)

            if orgEmail:
                details['htmlOrganizer'] = '<a href="mailto:%s">%s</a>' % (
                    orgEmail, orgCN)
            else:
                details['htmlOrganizer'] = orgCN

            details['iconName'] = iconName = "calicon.png"

            templateDir = config.Scheduling.iMIP.MailTemplatesDirectory.rstrip("/")
            templateName = "cancel.html" if canceled else "invite.html"
            templatePath = os.path.join(templateDir, templateName)

            if not os.path.exists(templatePath):
                # Fall back to built-in simple templates:
                if canceled:

                    htmlTemplate = u"""<html>
    <body><div>

    <h1>%(subject)s</h1>
    <p>
    <h3>%(orgLabel)s:</h3> %(htmlOrganizer)s
    </p>
    <p>
    <h3>%(dateLabel)s:</h3> %(dateInfo)s %(recurrenceInfo)s
    </p>
    <p>
    <h3>%(timeLabel)s:</h3> %(timeInfo)s %(durationInfo)s
    </p>

    """

                else:

                    htmlTemplate = u"""<html>
    <body><div>
    <p>%(inviteLabel)s</p>

    <h1>%(summary)s</h1>
    <p>
    <h3>%(orgLabel)s:</h3> %(htmlOrganizer)s
    </p>
    <p>
    <h3>%(locLabel)s:</h3> %(location)s
    </p>
    <p>
    <h3>%(dateLabel)s:</h3> %(dateInfo)s %(recurrenceInfo)s
    </p>
    <p>
    <h3>%(timeLabel)s:</h3> %(timeInfo)s %(durationInfo)s
    </p>
    <p>
    <h3>%(descLabel)s:</h3> %(description)s
    </p>
    <p>
    <h3>%(attLabel)s:</h3> %(htmlAttendees)s
    </p>

    """
            else: # HTML template file exists

                with open(templatePath) as templateFile:
                    htmlTemplate = templateFile.read()

            htmlText = htmlTemplate % details

        msgHtml = MIMEText(htmlText.encode("UTF-8"), "html", "UTF-8")
        msgHtmlRelated.attach(msgHtml)

        # an image for html version
        if (iconPath != None and os.path.exists(iconPath) and
            htmlTemplate.find("cid:%(iconName)s") != -1):

            with open(iconPath) as iconFile:
                msgIcon = MIMEImage(iconFile.read(),
                    _subtype='png;x-apple-mail-type=stationery;name="%s"' %
                    (iconName,))

            msgIcon.add_header("Content-ID", "<%s>" % (iconName,))
            msgIcon.add_header("Content-Disposition", "inline;filename=%s" %
                (iconName,))
            msgHtmlRelated.attach(msgIcon)

        # the icalendar attachment
        self.log_debug("Mail gateway sending calendar body: %s" % (str(calendar)))
        msgIcal = MIMEText(str(calendar), "calendar", "UTF-8")
        method = calendar.propertyValue("METHOD").lower()
        msgIcal.set_param("method", method)
        msgIcal.add_header("Content-ID", "<invitation.ics>")
        msgIcal.add_header("Content-Disposition",
            "inline;filename=invitation.ics")
        msg.attach(msgIcal)

        return msgId, msg.as_string()
コード例 #38
0
ファイル: mimebakery.py プロジェクト: pombredanne/quotient
def createMessage(
    composer,
    cabinet,
    msgRepliedTo,
    fromAddress,
    toAddresses,
    subject,
    messageBody,
    cc,
    bcc,
    files,
    createMessageObject=None,
):
    """
    Create an outgoing message, format the body into MIME parts, and
    populate its headers.

    @param createMessageObject: A one-argument callable which will be
    invoked with a file-like object containing MIME text and which
    should return a Message instance associated with objects
    representing that MIME data.
    """
    MC.add_charset("utf-8", None, MC.QP, "utf-8")

    encode = lambda s: MH.Header(s).encode()

    s = S.StringIO()
    wrappedMsgBody = FlowedParagraph.fromRFC2646(messageBody).asRFC2646()
    m = MT.MIMEText(wrappedMsgBody, "plain", "utf-8")
    m.set_param("format", "flowed")

    fileItems = []
    if files:
        attachmentParts = []
        for storeID in files:
            a = composer.store.getItemByID(long(storeID))
            if isinstance(a, Part):
                a = cabinet.createFileItem(
                    a.getParam("filename", default=u"", header=u"content-disposition"),
                    unicode(a.getContentType()),
                    a.getBody(decode=True),
                )
            fileItems.append(a)
            attachmentParts.append(_fileItemToEmailPart(a))

        m = MMP.MIMEMultipart("mixed", None, [m] + attachmentParts)

    m["From"] = encode(fromAddress.address)
    m["To"] = encode(mimeutil.flattenEmailAddresses(toAddresses))
    m["Subject"] = encode(subject)
    m["Date"] = EU.formatdate()
    m["Message-ID"] = smtp.messageid("divmod.xquotient")

    if cc:
        m["Cc"] = encode(mimeutil.flattenEmailAddresses(cc))
    if msgRepliedTo is not None:
        # our parser does not remove continuation whitespace, so to
        # avoid duplicating it --
        refs = [hdr.value for hdr in msgRepliedTo.impl.getHeaders("References")]
        if len(refs) == 0:
            irt = [hdr.value for hdr in msgRepliedTo.impl.getHeaders("In-Reply-To")]
            if len(irt) == 1:
                refs = irt
            else:
                refs = []
        msgids = msgRepliedTo.impl.getHeaders("Message-ID")
        for hdr in msgids:
            msgid = hdr.value
            refs.append(msgid)
            # As far as I can tell, the email package doesn't handle
            # multiple values for headers automatically, so here's some
            # continuation whitespace.
            m["References"] = u"\n\t".join(refs)
            m["In-Reply-To"] = msgid
            break
    G.Generator(s).flatten(m)
    s.seek(0)

    if createMessageObject is None:

        def createMessageObject(messageFile):
            return composer.createMessageAndQueueIt(fromAddress.address, messageFile, True)

    msg = createMessageObject(s)

    # there is probably a better way than this, but there
    # isn't a way to associate the same file item with multiple
    # messages anyway, so there isn't a need to reflect that here
    for fileItem in fileItems:
        fileItem.message = msg
    return msg
コード例 #39
0
 def generateMagic(self):
     return smtp.messageid()
コード例 #40
0
    def generateEmail(self, inviteState, calendar, orgEmail, orgCN,
                      attendees, fromAddress, replyToAddress, toAddress,
                      language='en'):
        """
        Generate MIME text containing an iMIP invitation, cancellation, update
        or reply.

        @param inviteState: 'new', 'update', or 'reply'.

        @type inviteState: C{str}

        @param calendar: the iCalendar component to attach to the email.

        @type calendar: L{twistedcaldav.ical.Component}

        @param orgEmail: The email for the organizer, in C{localhost@domain}
            format, or C{None} if the organizer has no email address.

        @type orgEmail: C{str} or C{NoneType}

        @param orgCN: Common name / display name for the organizer.

        @type orgCN: C{unicode}

        @param attendees: A C{list} of 2-C{tuple}s of (common name, email
            address) similar to (orgEmail, orgCN).

        @param fromAddress: the address to use in the C{From:} header of the
            email.

        @type fromAddress: C{str}

        @param replyToAddress: the address to use in the C{Reply-To} header.

        @type replyToAddress: C{str}

        @param toAddress: the address to use in the C{To} header.

        @type toAddress: C{str}

        @param language: a 2-letter language code describing the target
            language that the email should be generated in.

        @type language: C{str}

        @return: a 2-tuple of C{str}s: (message ID, message text).  The message
            ID is the value of the C{Message-ID} header, and the message text is
            the full MIME message, ready for transport over SMTP.
        """

        details = self.getEventDetails(calendar, language=language)
        canceled = (calendar.propertyValue("METHOD") == "CANCEL")

        subjectFormat, labels = localizedLabels(language, canceled, inviteState)
        details.update(labels)

        details['subject'] = subjectFormat % {'summary' : details['summary']}

        plainText = self.renderPlainText(details, (orgCN, orgEmail),
                                         attendees, canceled)

        htmlText = self.renderHTML(details, (orgCN, orgEmail),
                                              attendees, canceled)

        msg = MIMEMultipart()
        msg["From"] = fromAddress
        msg["Subject"] = details['subject']
        msg["Reply-To"] = replyToAddress
        msg["To"] = toAddress
        msg["Date"] = rfc822date()
        msgId = messageid()
        msg["Message-ID"] = msgId

        msgAlt = MIMEMultipart("alternative")
        msg.attach(msgAlt)

        # plain version
        msgPlain = MIMEText(plainText, "plain", "UTF-8")
        msgAlt.attach(msgPlain)

        # html version
        msgHtmlRelated = MIMEMultipart("related", type="text/html")
        msgAlt.attach(msgHtmlRelated)

        msgHtml = MIMEText(htmlText, "html", "UTF-8")
        msgHtmlRelated.attach(msgHtml)

        calendarText = str(calendar)
        # the icalendar attachment
        self.log.debug("Mail gateway sending calendar body: %s"
                       % (calendarText,))
        msgIcal = MIMEText(calendarText, "calendar", "UTF-8")
        method = calendar.propertyValue("METHOD").lower()
        msgIcal.set_param("method", method)
        msgIcal.add_header("Content-ID", "<invitation.ics>")
        msgIcal.add_header("Content-Disposition",
            "inline;filename=invitation.ics")
        msg.attach(msgIcal)

        return msgId, msg.as_string()
コード例 #41
0
ファイル: pop3.py プロジェクト: AnthonyNystrom/YoGoMee
 def generateMagic(self):
     return smtp.messageid()
コード例 #42
0
 def testMessageID(self):
     d = {}
     for i in range(1000):
         m = smtp.messageid('testcase')
         self.failIf(m in d)
         d[m] = None
コード例 #43
0
ファイル: outbound.py プロジェクト: gingerkaan/serveros
    def generateEmail(self,
                      inviteState,
                      calendar,
                      orgEmail,
                      orgCN,
                      attendees,
                      fromAddress,
                      replyToAddress,
                      toAddress,
                      language='en'):
        """
        Generate MIME text containing an iMIP invitation, cancellation, update
        or reply.

        @param inviteState: 'new', 'update', or 'reply'.

        @type inviteState: C{str}

        @param calendar: the iCalendar component to attach to the email.

        @type calendar: L{twistedcaldav.ical.Component}

        @param orgEmail: The email for the organizer, in C{localhost@domain}
            format, or C{None} if the organizer has no email address.

        @type orgEmail: C{str} or C{NoneType}

        @param orgCN: Common name / display name for the organizer.

        @type orgCN: C{unicode}

        @param attendees: A C{list} of 2-C{tuple}s of (common name, email
            address) similar to (orgEmail, orgCN).

        @param fromAddress: the address to use in the C{From:} header of the
            email.

        @type fromAddress: C{str}

        @param replyToAddress: the address to use in the C{Reply-To} header.

        @type replyToAddress: C{str}

        @param toAddress: the address to use in the C{To} header.

        @type toAddress: C{str}

        @param language: a 2-letter language code describing the target
            language that the email should be generated in.

        @type language: C{str}

        @return: a 2-tuple of C{str}s: (message ID, message text).  The message
            ID is the value of the C{Message-ID} header, and the message text is
            the full MIME message, ready for transport over SMTP.
        """

        details = self.getEventDetails(calendar, language=language)
        canceled = (calendar.propertyValue("METHOD") == "CANCEL")

        subjectFormat, labels = localizedLabels(language, canceled,
                                                inviteState)
        details.update(labels)

        details['subject'] = subjectFormat % {'summary': details['summary']}

        plainText = self.renderPlainText(details, (orgCN, orgEmail), attendees,
                                         canceled)

        htmlText = self.renderHTML(details, (orgCN, orgEmail), attendees,
                                   canceled)

        msg = MIMEMultipart()
        msg["From"] = fromAddress
        msg["Subject"] = details['subject']
        msg["Reply-To"] = replyToAddress
        msg["To"] = toAddress
        msg["Date"] = rfc822date()
        msgId = messageid()
        msg["Message-ID"] = msgId

        msgAlt = MIMEMultipart("alternative")
        msg.attach(msgAlt)

        # plain version
        msgPlain = MIMEText(plainText, "plain", "UTF-8")
        msgAlt.attach(msgPlain)

        # html version
        msgHtmlRelated = MIMEMultipart("related", type="text/html")
        msgAlt.attach(msgHtmlRelated)

        msgHtml = MIMEText(htmlText, "html", "UTF-8")
        msgHtmlRelated.attach(msgHtml)

        # the icalendar attachment

        # Make sure we always have the timezones used in the calendar data as iMIP requires VTIMEZONE
        # always be present (i.e., timezones-by-reference is not allowed in iMIP).
        calendarText = calendar.getTextWithTimezones(includeTimezones=True)

        self.log.debug("Mail gateway sending calendar body: %s" %
                       (calendarText, ))
        msgIcal = MIMEText(calendarText, "calendar", "UTF-8")
        method = calendar.propertyValue("METHOD").lower()
        msgIcal.set_param("method", method)
        msgIcal.add_header("Content-ID", "<invitation.ics>")
        msgIcal.add_header("Content-Disposition",
                           "inline;filename=invitation.ics")
        msg.attach(msgIcal)

        return msgId, msg.as_string()