예제 #1
0
def encodePassword(plaintext, scheme, other=None, config=None):
    """Encrypt the plaintext password.
    """
    if plaintext is None:
        plaintext = ""
    if scheme == "PBKDF2":
        if other:
            rounds, salt, raw_salt, digest = pbkdf2_unpack(other)
        else:
            raw_salt = random_.token_bytes(20)
            salt = h64encode(raw_salt)
            if config:
                rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS
            else:
                rounds = 10000
        if rounds < 1000:
            raise PasswordValueError("invalid PBKDF2 hash (rounds too low)")
        raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20)
        return "%d$%s$%s" % (rounds, salt, h64encode(raw_digest))
    elif scheme == 'SSHA':
        if other:
            raw_other = b64decode(other)
            salt = raw_other[20:]
        else:
            # new password
            # variable salt length
            salt_len = random_.randbelow(52 - 36) + 36
            salt = random_.token_bytes(salt_len)
        s = ssha(s2b(plaintext), salt)
    elif scheme == 'SHA':
        s = sha1(s2b(plaintext)).hexdigest()  # nosec
    elif scheme == 'MD5':
        s = md5(s2b(plaintext)).hexdigest()  # nosec
    elif scheme == 'crypt':
        if crypt is None:
            raise PasswordValueError('Unsupported encryption scheme %r' %
                                     scheme)
        if other is not None:
            salt = other
        else:
            saltchars = './0123456789' + string.ascii_letters
            salt = random_.choice(saltchars) + random_.choice(saltchars)
        s = crypt.crypt(plaintext, salt)
    elif scheme == 'plaintext':
        s = plaintext
    else:
        raise PasswordValueError('Unknown encryption scheme %r' % scheme)
    return s
예제 #2
0
    def send_message(self,
                     issueid,
                     msgid,
                     note,
                     sendto,
                     from_address=None,
                     bcc_sendto=[],
                     subject=None,
                     crypt=False,
                     add_headers={},
                     authid=None):
        '''Actually send the nominated message from this issue to the sendto
           recipients, with the note appended. It's possible to add
           headers to the message with the add_headers variable.
        '''
        users = self.db.user
        messages = self.db.msg
        files = self.db.file

        if msgid is None:
            inreplyto = None
            messageid = None
        else:
            inreplyto = messages.get(msgid, 'inreplyto')
            messageid = messages.get(msgid, 'messageid')

        # make up a messageid if there isn't one (web edit)
        if not messageid:
            # this is an old message that didn't get a messageid, so
            # create one
            messageid = "<%s.%s.%s%s@%s>" % (
                time.time(), b2s(base64.b32encode(random_.token_bytes(10))),
                self.classname, issueid, self.db.config['MAIL_DOMAIN'])
            if msgid is not None:
                messages.set(msgid, messageid=messageid)

        # compose title
        cn = self.classname
        title = self.get(issueid, 'title') or '%s message copy' % cn

        # figure author information
        if authid:
            pass
        elif msgid:
            authid = messages.get(msgid, 'author')
        else:
            authid = self.db.getuid()
        authname = users.get(authid, 'realname')
        if not authname:
            authname = users.get(authid, 'username', '')
        authaddr = users.get(authid, 'address', '')

        if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL:
            authaddr = " <%s>" % formataddr(('', authaddr))
        elif authaddr:
            authaddr = ""

        # make the message body
        m = ['']

        # put in roundup's signature
        if self.db.config.EMAIL_SIGNATURE_POSITION == 'top':
            m.append(self.email_signature(issueid, msgid))

        # add author information
        if authid and self.db.config.MAIL_ADD_AUTHORINFO:
            if msgid and len(self.get(issueid, 'messages')) == 1:
                m.append(
                    _("New submission from %(authname)s%(authaddr)s:") %
                    locals())
            elif msgid:
                m.append(
                    _("%(authname)s%(authaddr)s added the comment:") %
                    locals())
            else:
                m.append(_("Change by %(authname)s%(authaddr)s:") % locals())
            m.append('')

        # add the content
        if msgid is not None:
            m.append(messages.get(msgid, 'content', ''))

        # get the files for this message
        message_files = []
        if msgid:
            for fileid in messages.get(msgid, 'files'):
                # check the attachment size
                filesize = self.db.filesize('file', fileid, None)
                if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE:
                    message_files.append(fileid)
                else:
                    base = self.db.config.TRACKER_WEB
                    link = "".join((base, files.classname, fileid))
                    filename = files.get(fileid, 'name')
                    m.append(
                        _("File '%(filename)s' not attached - "
                          "you can download it from %(link)s.") % locals())

        # add the change note
        if note:
            m.append(note)

        # put in roundup's signature
        if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom':
            m.append(self.email_signature(issueid, msgid))

        # figure the encoding
        charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8')

        # construct the content and convert to unicode object
        body = s2u('\n'.join(m))

        # make sure the To line is always the same (for testing mostly)
        sendto.sort()

        # make sure we have a from address
        if from_address is None:
            from_address = self.db.config.TRACKER_EMAIL

        # additional bit for after the From: "name"
        from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '')
        if from_tag:
            from_tag = ' ' + from_tag

        if subject is None:
            subject = '[%s%s] %s' % (cn, issueid, title)

        author = (authname + from_tag, from_address)

        # send an individual message per recipient?
        if self.db.config.NOSY_EMAIL_SENDING != 'single':
            sendto = [[address] for address in sendto]
        else:
            sendto = [sendto]

        # tracker sender info
        tracker_name = s2u(self.db.config.TRACKER_NAME)
        tracker_name = nice_sender_header(tracker_name, from_address, charset)

        # now send one or more messages
        # TODO: I believe we have to create a new message each time as we
        # can't fiddle the recipients in the message ... worth testing
        # and/or fixing some day
        first = True
        for sendto in sendto:
            # create the message
            mailer = Mailer(self.db.config)

            message = mailer.get_standard_message(multipart=message_files)

            # set reply-to as requested by config option
            # TRACKER_REPLYTO_ADDRESS
            replyto_config = self.db.config.TRACKER_REPLYTO_ADDRESS
            if replyto_config:
                if replyto_config == "AUTHOR":
                    # note that authaddr at this point is already
                    # surrounded by < >, so get the original address
                    # from the db as nice_send_header adds < >
                    replyto_addr = nice_sender_header(
                        authname, users.get(authid, 'address', ''), charset)
                else:
                    replyto_addr = replyto_config
            else:
                replyto_addr = tracker_name
            message['Reply-To'] = replyto_addr

            # message ids
            if messageid:
                message['Message-Id'] = messageid
            if inreplyto:
                message['In-Reply-To'] = inreplyto

            # Generate a header for each link or multilink to
            # a class that has a name attribute
            for propname, prop in self.getprops().items():
                if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)):
                    continue
                cl = self.db.getclass(prop.classname)
                label = None
                if 'name' in cl.getprops():
                    label = 'name'
                if prop.msg_header_property in cl.getprops():
                    label = prop.msg_header_property
                if prop.msg_header_property == "":
                    # if msg_header_property is set to empty string
                    # suppress the header entirely. You can't use
                    # 'msg_header_property == None'. None is the
                    # default value.
                    label = None
                if not label:
                    continue
                if isinstance(prop, hyperdb.Link):
                    value = self.get(issueid, propname)
                    if value is None:
                        continue
                    values = [value]
                else:
                    values = self.get(issueid, propname)
                    if not values:
                        continue
                values = [cl.get(v, label) for v in values]
                values = ', '.join(values)
                header = "X-Roundup-%s-%s" % (self.classname, propname)
                try:
                    values.encode('ascii')
                    message[header] = values
                except UnicodeError:
                    message[header] = Header(values, charset)

            # Add header for main id number to make filtering
            # email easier than extracting from subject line.
            header = "X-Roundup-%s-Id" % (self.classname)
            values = issueid
            try:
                values.encode('ascii')
                message[header] = values
            except UnicodeError:
                message[header] = Header(values, charset)
            # Generate additional headers
            for k in add_headers:
                v = add_headers[k]
                try:
                    v.encode('ascii')
                    message[k] = v
                except UnicodeError:
                    message[k] = Header(v, charset)

            if not inreplyto:
                # Default the reply to the first message
                msgs = self.get(issueid, 'messages')
                # Assume messages are sorted by increasing message number here
                # If the issue is just being created, and the submitter didn't
                # provide a message, then msgs will be empty.
                if msgs and msgs[0] != msgid:
                    inreplyto = messages.get(msgs[0], 'messageid')
                    if inreplyto:
                        message['In-Reply-To'] = inreplyto

            # attach files
            if message_files:
                # first up the text as a part
                part = mailer.get_standard_message()
                part.set_payload(body, part.get_charset())
                message.attach(part)

                for fileid in message_files:
                    name = files.get(fileid, 'name')
                    mime_type = (files.get(fileid, 'type')
                                 or mimetypes.guess_type(name)[0]
                                 or 'application/octet-stream')
                    if mime_type == 'text/plain':
                        content = files.get(fileid, 'content')
                        part = MIMEText('')
                        del part['Content-Transfer-Encoding']
                        try:
                            enc = content.encode('ascii')
                            part = mailer.get_text_message('us-ascii')
                            part.set_payload(enc)
                        except UnicodeError:
                            # the content cannot be 7bit-encoded.
                            # use quoted printable
                            # XXX stuffed if we know the charset though :(
                            part = mailer.get_text_message('utf-8')
                            part.set_payload(content, part.get_charset())
                    elif mime_type == 'message/rfc822':
                        content = files.get(fileid, 'content')
                        main, sub = mime_type.split('/')
                        p = FeedParser()
                        p.feed(content)
                        part = MIMEBase(main, sub)
                        part.set_payload([p.close()])
                    else:
                        # some other type, so encode it
                        content = files.get(fileid, 'binary_content')
                        main, sub = mime_type.split('/')
                        part = MIMEBase(main, sub)
                        part.set_payload(content)
                        encoders.encode_base64(part)
                    cd = 'Content-Disposition'
                    part[cd] = 'attachment;\n filename="%s"' % name
                    message.attach(part)

            else:
                message.set_payload(body, message.get_charset())

            if crypt:
                send_msg = self.encrypt_to(message, sendto)
            else:
                send_msg = message
            mailer.set_message_attributes(send_msg, sendto, subject, author)
            if crypt:
                send_msg['Message-Id'] = message['Message-Id']
                send_msg['Reply-To'] = message['Reply-To']
                if message.get('In-Reply-To'):
                    send_msg['In-Reply-To'] = message['In-Reply-To']

            if sendto:
                mailer.smtp_send(sendto, send_msg.as_string())
            if first:
                if crypt:
                    # send individual bcc mails, otherwise receivers can
                    # deduce bcc recipients from keys in message
                    for bcc in bcc_sendto:
                        send_msg = self.encrypt_to(message, [bcc])
                        send_msg['Message-Id'] = message['Message-Id']
                        send_msg['Reply-To'] = message['Reply-To']
                        if message.get('In-Reply-To'):
                            send_msg['In-Reply-To'] = message['In-Reply-To']
                        mailer.smtp_send([bcc], send_msg.as_string())
                elif bcc_sendto:
                    mailer.smtp_send(bcc_sendto, send_msg.as_string())
            first = False