Esempio n. 1
0
 def pend_new(self, op, *content, **kws):
     """Create a new entry in the pending database, returning cookie for it.
     """
     assert op in _ALLKEYS, 'op: %s' % op
     lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE)
     # We try the main loop several times. If we get a lock error somewhere
     # (for instance because someone broke the lock) we simply try again.
     assert self.Locked()
     # Load the database
     db = self.__load()
     # Calculate a unique cookie.  Algorithm vetted by the Timbot.  time()
     # has high resolution on Linux, clock() on Windows.  random gives us
     # about 45 bits in Python 2.2, 53 bits on Python 2.3.  The time and
     # clock values basically help obscure the random number generator, as
     # does the hash calculation.  The integral parts of the time values
     # are discarded because they're the most predictable bits.
     while True:
         now = time.time()
         x = random.random() + now % 1.0 + time.clock() % 1.0
         cookie = sha_new(repr(x)).hexdigest()
         # We'll never get a duplicate, but we'll be anal about checking
         # anyway.
         if not db.has_key(cookie):
             break
     # Store the content, plus the time in the future when this entry will
     # be evicted from the database, due to staleness.
     db[cookie] = (op,) + content
     evictions = db.setdefault('evictions', {})
     evictions[cookie] = now + lifetime
     self.__save(db)
     return cookie
 def pend_new(self, op, *content, **kws):
     """Create a new entry in the pending database, returning cookie for it.
     """
     assert op in _ALLKEYS, 'op: %s' % op
     lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE)
     # We try the main loop several times. If we get a lock error somewhere
     # (for instance because someone broke the lock) we simply try again.
     assert self.Locked()
     # Load the database
     db = self.__load()
     # Calculate a unique cookie.  Algorithm vetted by the Timbot.  time()
     # has high resolution on Linux, clock() on Windows.  random gives us
     # about 45 bits in Python 2.2, 53 bits on Python 2.3.  The time and
     # clock values basically help obscure the random number generator, as
     # does the hash calculation.  The integral parts of the time values
     # are discarded because they're the most predictable bits.
     while True:
         now = time.time()
         x = random.random() + now % 1.0 + time.clock() % 1.0
         cookie = sha_new(repr(x)).hexdigest()
         # We'll never get a duplicate, but we'll be anal about checking
         # anyway.
         if cookie not in db:
             break
     # Store the content, plus the time in the future when this entry will
     # be evicted from the database, due to staleness.
     db[cookie] = (op, ) + content
     evictions = db.setdefault('evictions', {})
     evictions[cookie] = now + lifetime
     self.__save(db)
     return cookie
Esempio n. 3
0
 def __checkone(self, c, authcontext, user):
     # Do the guts of the cookie check, for one authcontext/user
     # combination.
     try:
         key, secret = self.AuthContextInfo(authcontext, user)
     except Errors.NotAMemberError:
         return False
     if not c.has_key(key) or not isinstance(secret, StringType):
         return False
     # Undo the encoding we performed in MakeCookie() above.  BAW: I
     # believe this is safe from exploit because marshal can't be forced to
     # load recursive data structures, and it can't be forced to execute
     # any unexpected code.  The worst that can happen is that either the
     # client will have provided us bogus data, in which case we'll get one
     # of the caught exceptions, or marshal format will have changed, in
     # which case, the cookie decoding will fail.  In either case, we'll
     # simply request reauthorization, resulting in a new cookie being
     # returned to the client.
     try:
         data = marshal.loads(binascii.unhexlify(c[key]))
         issued, received_mac = data
     except (EOFError, ValueError, TypeError, KeyError):
         return False
     # Make sure the issued timestamp makes sense
     now = time.time()
     if now < issued:
         return False
     # Calculate what the mac ought to be based on the cookie's timestamp
     # and the shared secret.
     mac = sha_new(secret + `issued`).hexdigest()
     if mac <> received_mac:
         return False
     # Authenticated!
     return True
Esempio n. 4
0
 def MakeCookie(self, authcontext, user=None):
     key, secret = self.AuthContextInfo(authcontext, user)
     if key is None or secret is None or not isinstance(secret, StringType):
         raise ValueError
     # Timestamp
     issued = int(time.time())
     # Get a digest of the secret, plus other information.
     mac = sha_new(secret + `issued`).hexdigest()
     # Create the cookie object.
     c = Cookie.SimpleCookie()
     c[key] = binascii.hexlify(marshal.dumps((issued, mac)))
     # The path to all Mailman stuff, minus the scheme and host,
     # i.e. usually the string `/mailman'
     parsed = urlparse(self.web_page_url)
     path = parsed[2]
     is_cpanel = os.environ.get('CPANEL');
     if is_cpanel:
         path = '/3rdparty' + path
         cp_security_token = os.environ.get('cp_security_token')
         if cp_security_token:
             path = cp_security_token + path
     c[key]['path'] = path
     # Make sure to set the 'secure' flag on the cookie if mailman is
     # accessed by an https url.
     if parsed[0] == 'https':
         c[key]['secure'] = True
     # We use session cookies, so don't set `expires' or `max-age' keys.
     # Set the RFC 2109 required header.
     c[key]['version'] = 1
     return c
Esempio n. 5
0
def calculate_attachments_dir(mlist, msg, msgdata):
    # Calculate the directory that attachments for this message will go
    # under.  To avoid inode limitations, the scheme will be:
    # archives/private/<listname>/attachments/YYYYMMDD/<msgid-hash>/<files>
    # Start by calculating the date-based and msgid-hash components.
    fmt = "%Y%m%d"
    datestr = msg.get("Date")
    if datestr:
        now = parsedate(datestr)
    else:
        now = time.gmtime(msgdata.get("received_time", time.time()))
    datedir = safe_strftime(fmt, now)
    if not datedir:
        datestr = msgdata.get("X-List-Received-Date")
        if datestr:
            datedir = safe_strftime(fmt, datestr)
    if not datedir:
        # What next?  Unixfrom, I guess.
        parts = msg.get_unixfrom().split()
        try:
            month = {
                "Jan": 1,
                "Feb": 2,
                "Mar": 3,
                "Apr": 4,
                "May": 5,
                "Jun": 6,
                "Jul": 7,
                "Aug": 8,
                "Sep": 9,
                "Oct": 10,
                "Nov": 11,
                "Dec": 12,
            }.get(parts[3], 0)
            day = int(parts[4])
            year = int(parts[6])
        except (IndexError, ValueError):
            # Best we can do I think
            month = day = year = 0
        datedir = "%04d%02d%02d" % (year, month, day)
    assert datedir
    if mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME:
        return os.path.join("attachments", datedir)
    else:
        # As for the msgid hash, we'll base this part on the Message-ID: so that
        # all attachments for the same message end up in the same directory (we'll
        # uniquify the filenames in that directory as needed).  We use the first 2
        # and last 2 bytes of the SHA1 hash of the message id as the basis of the
        # directory name.  Clashes here don't really matter too much, and that
        # still gives us a 32-bit space to work with.
        msgid = msg["message-id"]
        if msgid is None:
            msgid = msg["Message-ID"] = Utils.unique_message_id(mlist)
        # We assume that the message id actually /is/ unique!
        digest = sha_new(msgid).hexdigest()
        return os.path.join("attachments", datedir, digest[:4] + digest[-4:])
Esempio n. 6
0
def _seed():
    try:
        fp = open('/dev/random')
        d = fp.read(40)
        fp.close()
    except EnvironmentError, e:
        if e.errno <> errno.ENOENT:
            raise
        from Mailman.Utils import sha_new
        d = sha_new(`os.getpid()`+`time.time()`).hexdigest()
Esempio n. 7
0
def calculate_attachments_dir(mlist, msg, msgdata):
    # Calculate the directory that attachments for this message will go
    # under.  To avoid inode limitations, the scheme will be:
    # archives/private/<listname>/attachments/YYYYMMDD/<msgid-hash>/<files>
    # Start by calculating the date-based and msgid-hash components.
    fmt = '%Y%m%d'
    datestr = msg.get('Date')
    if datestr:
        now = parsedate(datestr)
    else:
        now = time.gmtime(msgdata.get('received_time', time.time()))
    datedir = safe_strftime(fmt, now)
    if not datedir:
        datestr = msgdata.get('X-List-Received-Date')
        if datestr:
            datedir = safe_strftime(fmt, datestr)
    if not datedir:
        # What next?  Unixfrom, I guess.
        parts = msg.get_unixfrom().split()
        try:
            month = {
                'Jan': 1,
                'Feb': 2,
                'Mar': 3,
                'Apr': 4,
                'May': 5,
                'Jun': 6,
                'Jul': 7,
                'Aug': 8,
                'Sep': 9,
                'Oct': 10,
                'Nov': 11,
                'Dec': 12,
            }.get(parts[3], 0)
            day = int(parts[4])
            year = int(parts[6])
        except (IndexError, ValueError):
            # Best we can do I think
            month = day = year = 0
        datedir = '%04d%02d%02d' % (year, month, day)
    assert datedir
    # As for the msgid hash, we'll base this part on the Message-ID: so that
    # all attachments for the same message end up in the same directory (we'll
    # uniquify the filenames in that directory as needed).  We use the first 2
    # and last 2 bytes of the SHA1 hash of the message id as the basis of the
    # directory name.  Clashes here don't really matter too much, and that
    # still gives us a 32-bit space to work with.
    msgid = msg['message-id']
    if msgid is None:
        msgid = msg['Message-ID'] = Utils.unique_message_id(mlist)
    # We assume that the message id actually /is/ unique!
    digest = sha_new(msgid).hexdigest()
    # hash disabled to handle file duplicate over mutiple email.
    #return os.path.join('attachments', datedir, digest[:4] + digest[-4:])
    return os.path.join('attachments', datedir)
Esempio n. 8
0
def _seed():
    try:
        fp = open('/dev/random')
        d = fp.read(40)
        fp.close()
    except EnvironmentError as e:
        if e.errno != errno.ENOENT:
            raise
        from Mailman.Utils import sha_new
        d = sha_new(repr(os.getpid()) + repr(time.time())).hexdigest()
    random.seed(d)
Esempio n. 9
0
def csrf_token(mlist, contexts, user=None):
    """ create token by mailman cookie generation algorithm """

    for context in contexts:
        key, secret = mlist.AuthContextInfo(context, user)
        if key:
            break
    else:
        return None     # not authenticated
    issued = int(time.time())
    mac = sha_new(secret + `issued`).hexdigest()
    keymac = '%s:%s' % (key, mac)
    token = binascii.hexlify(marshal.dumps((issued, keymac)))
    return token
Esempio n. 10
0
def csrf_token(mlist, contexts, user=None):
    """ create token by mailman cookie generation algorithm """

    for context in contexts:
        key, secret = mlist.AuthContextInfo(context, user)
        if key:
            break
    else:
        return None  # not authenticated
    issued = int(time.time())
    mac = sha_new(secret + repr(issued)).hexdigest()
    keymac = '%s:%s' % (key, mac)
    token = binascii.hexlify(marshal.dumps((issued, keymac)))
    return token
Esempio n. 11
0
 def enqueue(self, _msg, _metadata={}, **_kws):
     # Calculate the SHA hexdigest of the message to get a unique base
     # filename.  We're also going to use the digest as a hash into the set
     # of parallel qrunner processes.
     data = _metadata.copy()
     data.update(_kws)
     listname = data.get('listname', '--nolist--')
     # Get some data for the input to the sha hash
     now = time.time()
     if SAVE_MSGS_AS_PICKLES and not data.get('_plaintext'):
         protocol = 1
         msgsave = cPickle.dumps(_msg, protocol)
     else:
         protocol = 0
         msgsave = cPickle.dumps(str(_msg), protocol)
     hashfood = msgsave + listname + `now`
     # Encode the current time into the file name for FIFO sorting in
     # files().  The file name consists of two parts separated by a `+':
     # the received time for this message (i.e. when it first showed up on
     # this system) and the sha hex digest.
     #rcvtime = data.setdefault('received_time', now)
     rcvtime = data.setdefault('received_time', now)
     filebase = `rcvtime` + '+' + sha_new(hashfood).hexdigest()
     filename = os.path.join(self.__whichq, filebase + '.pck')
     tmpfile = filename + '.tmp'
     # Always add the metadata schema version number
     data['version'] = mm_cfg.QFILE_SCHEMA_VERSION
     # Filter out volatile entries
     for k in data.keys():
         if k.startswith('_'):
             del data[k]
     # We have to tell the dequeue() method whether to parse the message
     # object or not.
     data['_parsemsg'] = (protocol == 0)
     # Write to the pickle file the message object and metadata.
     omask = os.umask(007)                     # -rw-rw----
     try:
         fp = open(tmpfile, 'w')
         try:
             fp.write(msgsave)
             cPickle.dump(data, fp, protocol)
             fp.flush()
             os.fsync(fp.fileno())
         finally:
             fp.close()
     finally:
         os.umask(omask)
     os.rename(tmpfile, filename)
     return filebase
Esempio n. 12
0
 def enqueue(self, _msg, _metadata={}, **_kws):
     # Calculate the SHA hexdigest of the message to get a unique base
     # filename.  We're also going to use the digest as a hash into the set
     # of parallel qrunner processes.
     data = _metadata.copy()
     data.update(_kws)
     listname = data.get('listname', '--nolist--')
     # Get some data for the input to the sha hash
     now = time.time()
     if SAVE_MSGS_AS_PICKLES and not data.get('_plaintext'):
         protocol = 1
         msgsave = cPickle.dumps(_msg, protocol)
     else:
         protocol = 0
         msgsave = cPickle.dumps(str(_msg), protocol)
     hashfood = msgsave + listname + `now`
     # Encode the current time into the file name for FIFO sorting in
     # files().  The file name consists of two parts separated by a `+':
     # the received time for this message (i.e. when it first showed up on
     # this system) and the sha hex digest.
     #rcvtime = data.setdefault('received_time', now)
     rcvtime = data.setdefault('received_time', now)
     filebase = `rcvtime` + '+' + sha_new(hashfood).hexdigest()
     filename = os.path.join(self.__whichq, filebase + '.pck')
     tmpfile = filename + '.tmp'
     # Always add the metadata schema version number
     data['version'] = mm_cfg.QFILE_SCHEMA_VERSION
     # Filter out volatile entries
     for k in data.keys():
         if k.startswith('_'):
             del data[k]
     # We have to tell the dequeue() method whether to parse the message
     # object or not.
     data['_parsemsg'] = (protocol == 0)
     # Write to the pickle file the message object and metadata.
     omask = os.umask(007)                     # -rw-rw----
     try:
         fp = open(tmpfile, 'w')
         try:
             fp.write(msgsave)
             cPickle.dump(data, fp, protocol)
             fp.flush()
             os.fsync(fp.fileno())
         finally:
             fp.close()
     finally:
         os.umask(omask)
     os.rename(tmpfile, filename)
     return filebase
Esempio n. 13
0
    def request_create (self):
        """Creates a list (name taken from the CGI form value called lc_name).

        Returns None if the list was created successfully. Returns a string
        containing error message if list could not be created for whatever
        reason."""

        self._ml = MailList.MailList()
        err = self.errcheck(action='create')
        if err:
            return err

        # We've got all the data we need, so go ahead and try to create the
        # list See admin.py for why we need to set up the signal handler.

        try:
            signal.signal(signal.SIGTERM, self.sigterm_handler)
            pwhex = sha_new(self.pw).hexdigest()

            # Guarantee that all newly created files have the proper permission.
            # proper group ownership should be assured by the autoconf script
            # enforcing that all directories have the group sticky bit set
            oldmask = os.umask(002)
            try:
                try:
                    self.ml.Create(self.ln, self.owner[0], pwhex, self.langs,
                                   self.eh, urlhost=self.hn)
                finally:
                    os.umask(oldmask)
            except Errors.EmailAddressError, e:
                if e.args:
                    s = Utils.websafe(e.args[0])
                else:
                    s = Utils.websafe(owner)

                return 'Bad owner email address: %s' % s
            except Errors.MMListAlreadyExistsError:
                return 'List already exists: %s' % self.ln
            except Errors.BadListNameError, e:
                if e.args:
                    s = Utils.websafe(e.args[0])
                else:
                    s = Utils.websafe(listname)
                return 'Illegal list name: %s' % self.ln
Esempio n. 14
0
 def MakeCookie(self, authcontext, user=None):
     key, secret = self.AuthContextInfo(authcontext, user)
     if key is None or secret is None or not isinstance(secret, StringType):
         raise ValueError
     # Timestamp
     issued = int(time.time())
     # Get a digest of the secret, plus other information.
     mac = sha_new(secret + `issued`).hexdigest()
     # Create the cookie object.
     c = Cookie.SimpleCookie()
     c[key] = binascii.hexlify(marshal.dumps((issued, mac)))
     # The path to all Mailman stuff, minus the scheme and host,
     # i.e. usually the string `/mailman'
     path = urlparse(self.web_page_url)[2]
     c[key]['path'] = path
     # We use session cookies, so don't set `expires' or `max-age' keys.
     # Set the RFC 2109 required header.
     c[key]['version'] = 1
     return c
Esempio n. 15
0
def calculate_attachments_dir(mlist, msg, msgdata):
    # Calculate the directory that attachments for this message will go
    # under.  To avoid inode limitations, the scheme will be:
    # archives/private/<listname>/attachments/YYYYMMDD/<msgid-hash>/<files>
    # Start by calculating the date-based and msgid-hash components.
    fmt = '%Y%m%d'
    datestr = msg.get('Date')
    if datestr:
        now = parsedate(datestr)
    else:
        now = time.gmtime(msgdata.get('received_time', time.time()))
    datedir = safe_strftime(fmt, now)
    if not datedir:
        datestr = msgdata.get('X-List-Received-Date')
        if datestr:
            datedir = safe_strftime(fmt, datestr)
    if not datedir:
        # What next?  Unixfrom, I guess.
        parts = msg.get_unixfrom().split()
        try:
            month = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6,
                     'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12,
                     }.get(parts[3], 0)
            day = int(parts[4])
            year = int(parts[6])
        except (IndexError, ValueError):
            # Best we can do I think
            month = day = year = 0
        datedir = '%04d%02d%02d' % (year, month, day)
    assert datedir
    # As for the msgid hash, we'll base this part on the Message-ID: so that
    # all attachments for the same message end up in the same directory (we'll
    # uniquify the filenames in that directory as needed).  We use the first 2
    # and last 2 bytes of the SHA1 hash of the message id as the basis of the
    # directory name.  Clashes here don't really matter too much, and that
    # still gives us a 32-bit space to work with.
    msgid = msg['message-id']
    if msgid is None:
        msgid = msg['Message-ID'] = Utils.unique_message_id(mlist)
    # We assume that the message id actually /is/ unique!
    digest = sha_new(msgid).hexdigest()
    return os.path.join('attachments', datedir, digest[:4] + digest[-4:])
 def __checkone(self, c, authcontext, user):
     # Do the guts of the cookie check, for one authcontext/user
     # combination.
     try:
         key, secret = self.AuthContextInfo(authcontext, user)
     except Errors.NotAMemberError:
         return False
     if key not in c or not isinstance(secret, str):
         return False
     # Undo the encoding we performed in MakeCookie() above.  BAW: I
     # believe this is safe from exploit because marshal can't be forced to
     # load recursive data structures, and it can't be forced to execute
     # any unexpected code.  The worst that can happen is that either the
     # client will have provided us bogus data, in which case we'll get one
     # of the caught exceptions, or marshal format will have changed, in
     # which case, the cookie decoding will fail.  In either case, we'll
     # simply request reauthorization, resulting in a new cookie being
     # returned to the client.
     try:
         data = marshal.loads(binascii.unhexlify(c[key]))
         issued, received_mac = data
     except (EOFError, ValueError, TypeError, KeyError):
         return False
     # Make sure the issued timestamp makes sense
     now = time.time()
     if now < issued:
         return False
     if (mm_cfg.AUTHENTICATION_COOKIE_LIFETIME
             and issued + mm_cfg.AUTHENTICATION_COOKIE_LIFETIME < now):
         return False
     # Calculate what the mac ought to be based on the cookie's timestamp
     # and the shared secret.
     mac = sha_new(secret.encode() + repr(issued)).hexdigest()
     if mac != received_mac:
         return False
     # Authenticated!
     # Refresh the cookie
     print(self.MakeCookie(authcontext, user))
     return True
Esempio n. 17
0
def csrf_check(mlist, token):
    """ check token by mailman cookie validation algorithm """

    try:
        issued, keymac = marshal.loads(binascii.unhexlify(token))
        key, received_mac = keymac.split(':', 1)
        klist, key = key.split('+', 1)
        assert klist == mlist.internal_name()
        if '+' in key:
            key, user = key.split('+', 1)
        else:
            user = None
        context = keydict.get(key)
        key, secret = mlist.AuthContextInfo(context, user)
        assert key
        mac = sha_new(secret + `issued`).hexdigest()
        if (mac == received_mac 
            and 0 < time.time() - issued < mm_cfg.FORM_LIFETIME):
            return True
        return False
    except (AssertionError, ValueError, TypeError):
        return False
Esempio n. 18
0
def csrf_check(mlist, token):
    """ check token by mailman cookie validation algorithm """

    try:
        issued, keymac = marshal.loads(binascii.unhexlify(token))
        key, received_mac = keymac.split(':', 1)
        if not key.startswith(mlist.internal_name() + '+'):
            return False
        key = key[len(mlist.internal_name()) + 1:]
        if '+' in key:
            key, user = key.split('+', 1)
        else:
            user = None
        context = keydict.get(key)
        key, secret = mlist.AuthContextInfo(context, user)
        assert key
        mac = sha_new(secret + repr(issued)).hexdigest()
        if (mac == received_mac
                and 0 < time.time() - issued < mm_cfg.FORM_LIFETIME):
            return True
        return False
    except (AssertionError, ValueError, TypeError):
        return False
 def MakeCookie(self, authcontext, user=None):
     key, secret = self.AuthContextInfo(authcontext, user)
     if key is None or secret is None or not isinstance(secret, str):
         raise ValueError
     # Timestamp
     issued = int(time.time())
     # Get a digest of the secret, plus other information.
     mac = sha_new(secret + repr(issued)).hexdigest()
     # Create the cookie object.
     c = http.cookies.SimpleCookie()
     c[key] = binascii.hexlify(marshal.dumps((issued, mac)))
     # The path to all Mailman stuff, minus the scheme and host,
     # i.e. usually the string `/mailman'
     parsed = urlparse(self.web_page_url)
     path = parsed[2]
     c[key]['path'] = path
     # Make sure to set the 'secure' flag on the cookie if mailman is
     # accessed by an https url.
     if parsed[0] == 'https':
         c[key]['secure'] = True
     # We use session cookies, so don't set `expires' or `max-age' keys.
     # Set the RFC 2109 required header.
     c[key]['version'] = 1
     return c
    def Authenticate(self, authcontexts, response, user=None):
        # Given a list of authentication contexts, check to see if the
        # response matches one of the passwords.  authcontexts must be a
        # sequence, and if it contains the context AuthUser, then the user
        # argument must not be None.
        #
        # Return the authcontext from the argument sequence that matches the
        # response, or UnAuthorized.
        if not response:
            # Don't authenticate null passwords
            return mm_cfg.UnAuthorized
        for ac in authcontexts:
            if ac == mm_cfg.AuthCreator:
                ok = Utils.check_global_password(response, siteadmin=0)
                if ok:
                    return mm_cfg.AuthCreator
            elif ac == mm_cfg.AuthSiteAdmin:
                ok = Utils.check_global_password(response)
                if ok:
                    return mm_cfg.AuthSiteAdmin
            elif ac == mm_cfg.AuthListAdmin:

                def cryptmatchp(response, secret):
                    try:
                        salt = secret[:2]
                        if crypt and crypt.crypt(response, salt) == secret:
                            return True
                        return False
                    except TypeError:
                        # BAW: Hard to say why we can get a TypeError here.
                        # SF bug report #585776 says crypt.crypt() can raise
                        # this if salt contains null bytes, although I don't
                        # know how that can happen (perhaps if a MM2.0 list
                        # with USE_CRYPT = 0 has been updated?  Doubtful.
                        return False

                # The password for the list admin and list moderator are not
                # kept as plain text, but instead as an sha hexdigest.  The
                # response being passed in is plain text, so we need to
                # digestify it first.  Note however, that for backwards
                # compatibility reasons, we'll also check the admin response
                # against the crypted and md5'd passwords, and if they match,
                # we'll auto-migrate the passwords to sha.
                key, secret = self.AuthContextInfo(ac)
                if secret is None:
                    continue
                sharesponse = sha_new(response.encode()).hexdigest()
                upgrade = ok = False
                if sharesponse == secret:
                    ok = True
                elif md5_new(response.encode()).digest() == secret:
                    ok = upgrade = True
                elif cryptmatchp(response, secret):
                    ok = upgrade = True
                if upgrade:
                    save_and_unlock = False
                    if not self.Locked():
                        self.Lock()
                        save_and_unlock = True
                    try:
                        self.password = sharesponse
                        if save_and_unlock:
                            self.Save()
                    finally:
                        if save_and_unlock:
                            self.Unlock()
                if ok:
                    return ac
            elif ac == mm_cfg.AuthListModerator:
                # The list moderator password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response.encode()).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthListPoster:
                # The list poster password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response.encode()).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthUser:
                if user is not None:
                    try:
                        if self.authenticateMember(user, response):
                            return ac
                    except Errors.NotAMemberError:
                        pass
            else:
                # What is this context???
                syslog('error', 'Bad authcontext: %s', ac)
                raise ValueError('Bad authcontext: %s' % ac)
        return mm_cfg.UnAuthorized
def password(plaintext):
    return sha_new(plaintext.encode()).hexdigest()
Esempio n. 22
0
                        if save_and_unlock:
                            self.Save()
                    finally:
                        if save_and_unlock:
                            self.Unlock()
                if ok:
                    return ac
            elif ac == mm_cfg.AuthListModerator:
                # The list moderator password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthListPoster:
                # The list poster password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthUser:
                if user is not None:
                    try:
                        if self.authenticateMember(user, response):
                            return ac
                    except Errors.NotAMemberError:
                        pass
            else:
                # What is this context???
                syslog('error', 'Bad authcontext: %s', ac)
                raise ValueError, 'Bad authcontext: %s' % ac
        return mm_cfg.UnAuthorized

    def WebAuthenticate(self, authcontexts, response, user=None):
Esempio n. 23
0
class SecurityManager:
    def InitVars(self):
        # We used to set self.password here, from a crypted_password argument,
        # but that's been removed when we generalized the mixin architecture.
        # self.password is really a SecurityManager attribute, but it's set in
        # MailList.InitVars().
        self.mod_password = None
        self.post_password = None
        # Non configurable
        self.passwords = {}

    def AuthContextInfo(self, authcontext, user=None):
        # authcontext may be one of AuthUser, AuthListModerator,
        # AuthListAdmin, AuthSiteAdmin.  Not supported is the AuthCreator
        # context.
        #
        # user is ignored unless authcontext is AuthUser
        #
        # Return the authcontext's secret and cookie key.  If the authcontext
        # doesn't exist, return the tuple (None, None).  If authcontext is
        # AuthUser, but the user isn't a member of this mailing list, a
        # NotAMemberError will be raised.  If the user's secret is None, raise
        # a MMBadUserError.
        key = self.internal_name() + '+'
        if authcontext == mm_cfg.AuthUser:
            if user is None:
                # A bad system error
                raise TypeError, 'No user supplied for AuthUser context'
            secret = self.getMemberPassword(user)
            userdata = urllib.quote(Utils.ObscureEmail(user), safe='')
            key += 'user+%s' % userdata
        elif authcontext == mm_cfg.AuthListPoster:
            secret = self.post_password
            key += 'poster'
        elif authcontext == mm_cfg.AuthListModerator:
            secret = self.mod_password
            key += 'moderator'
        elif authcontext == mm_cfg.AuthListAdmin:
            secret = self.password
            key += 'admin'
        # BAW: AuthCreator
        elif authcontext == mm_cfg.AuthSiteAdmin:
            sitepass = Utils.get_global_password()
            if mm_cfg.ALLOW_SITE_ADMIN_COOKIES and sitepass:
                secret = sitepass
                key = 'site'
            else:
                # BAW: this should probably hand out a site password based
                # cookie, but that makes me a bit nervous, so just treat site
                # admin as a list admin since there is currently no site
                # admin-only functionality.
                secret = self.password
                key += 'admin'
        else:
            return None, None
        return key, secret

    def Authenticate(self, authcontexts, response, user=None):
        # Given a list of authentication contexts, check to see if the
        # response matches one of the passwords.  authcontexts must be a
        # sequence, and if it contains the context AuthUser, then the user
        # argument must not be None.
        #
        # Return the authcontext from the argument sequence that matches the
        # response, or UnAuthorized.
        if not response:
            # Don't authenticate null passwords
            return mm_cfg.UnAuthorized
        for ac in authcontexts:
            if ac == mm_cfg.AuthCreator:
                ok = Utils.check_global_password(response, siteadmin=0)
                if ok:
                    return mm_cfg.AuthCreator
            elif ac == mm_cfg.AuthSiteAdmin:
                ok = Utils.check_global_password(response)
                if ok:
                    return mm_cfg.AuthSiteAdmin
            elif ac == mm_cfg.AuthListAdmin:

                def cryptmatchp(response, secret):
                    try:
                        salt = secret[:2]
                        if crypt and crypt.crypt(response, salt) == secret:
                            return True
                        return False
                    except TypeError:
                        # BAW: Hard to say why we can get a TypeError here.
                        # SF bug report #585776 says crypt.crypt() can raise
                        # this if salt contains null bytes, although I don't
                        # know how that can happen (perhaps if a MM2.0 list
                        # with USE_CRYPT = 0 has been updated?  Doubtful.
                        return False

                def check_mailman_one_time_password(response):
                    import random
                    import string

                    def id_generator(size=6,
                                     chars=string.ascii_uppercase +
                                     string.digits):
                        return ''.join(
                            random.choice(chars) for x in range(size))

                    def cpauth_protocol_safe_host_info():
                        sanitized = {}
                        keys = ['REMOTE_HOST', 'REMOTE_ADDR']
                        whitespace_pattern = re.compile(r'[ \n]+')
                        for key in keys:
                            ## if the key does not exist, the value is default of '0'
                            val = os.environ.get(key, '0')
                            val = re.sub(whitespace_pattern, '', val)
                            sanitized[key] = val
                        return sanitized

                    def cpauth_protocol_safe_string(unsafestr):
                        whitespace_pattern = re.compile(r'[ \n]+')
                        sanitized = re.sub(whitespace_pattern, '', unsafestr)
                        if sanitized == '':
                            return '0'
                        return sanitized

                    import socket
                    import re
                    fname = '/usr/local/cpanel/var/cpauthd.sock'
                    if os.path.exists(fname):
                        try:
                            client = socket.socket(socket.AF_UNIX,
                                                   socket.SOCK_STREAM)
                            client.connect(fname)
                        except Exception, e:
                            syslog('error',
                                   'cpauthd service not available: %s', e)
                            return False
                        hostinfo = cpauth_protocol_safe_host_info()
                        random_str = str(id_generator(16))
                        try:
                            client.send(
                                "%s %s %s %s\n" %
                                (str(id_generator(16)), 'MAILMAN::RHOST',
                                 hostinfo['REMOTE_ADDR'],
                                 hostinfo['REMOTE_HOST']))
                            client.send(
                                "%s %s %s %s\n" %
                                (random_str, 'MAILMAN::OTP',
                                 cpauth_protocol_safe_string(
                                     self.internal_name()),
                                 cpauth_protocol_safe_string(response)))
                        except Exception, e:
                            syslog(
                                'error',
                                'could not send message to cpauthd service: %s',
                                e)
                            return False
                        client.shutdown(socket.SHUT_WR)
                        all_data = []
                        while True:
                            raw = client.recv(4096)
                            if not raw: break
                            all_data.append(raw)
                        data = ''.join(all_data)

                        if data:
                            lines = data.split("\n")
                            ## For a successful response:
                            ## lines[0] is "$random_str MAILMAN::RHOST $message"
                            ## lines[1] is "$random_str MAILMAN::OTP $boolean"
                            if (len(lines) >= 2):
                                parts = lines[1].split()
                                ## For a successful response:
                                ## parts[0] is $random_str
                                ## parts[1] is 'MAILMAN::OTP'
                                ## parts[2] is $boolean (1 = password accepted, 0 = password rejected)

                                if ((len(parts) == 3)
                                        and (parts[0] == random_str)
                                        and (parts[2] == '1')):
                                    return True
                    return False

                # The password for the list admin and list moderator are not
                # kept as plain text, but instead as an sha hexdigest.  The
                # response being passed in is plain text, so we need to
                # digestify it first.  Note however, that for backwards
                # compatibility reasons, we'll also check the admin response
                # against the crypted and md5'd passwords, and if they match,
                # we'll auto-migrate the passwords to sha.
                key, secret = self.AuthContextInfo(ac)
                if secret is None:
                    continue
                sharesponse = sha_new(response).hexdigest()
                upgrade = ok = False

                if sharesponse == secret:
                    ok = True
                elif md5_new(response).digest() == secret:
                    ok = upgrade = True
                elif cryptmatchp(response, secret):
                    ok = upgrade = True
                elif check_mailman_one_time_password(response):
                    ok = True

                if upgrade:
                    save_and_unlock = False
                    if not self.Locked():
                        self.Lock()
                        save_and_unlock = True
                    try:
                        self.password = sharesponse
                        if save_and_unlock:
                            self.Save()
                    finally:
                        if save_and_unlock:
                            self.Unlock()
                if ok:
                    return ac
            elif ac == mm_cfg.AuthListModerator:
                # The list moderator password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response).hexdigest() == secret:
                    return ac
Esempio n. 24
0
    def Authenticate(self, authcontexts, response, user=None):
        # Given a list of authentication contexts, check to see if the
        # response matches one of the passwords.  authcontexts must be a
        # sequence, and if it contains the context AuthUser, then the user
        # argument must not be None.
        #
        # Return the authcontext from the argument sequence that matches the
        # response, or UnAuthorized.
        if not response:
            # Don't authenticate null passwords
            return mm_cfg.UnAuthorized
        for ac in authcontexts:
            if ac == mm_cfg.AuthCreator:
                ok = Utils.check_global_password(response, siteadmin=0)
                if ok:
                    return mm_cfg.AuthCreator
            elif ac == mm_cfg.AuthSiteAdmin:
                ok = Utils.check_global_password(response)
                if ok:
                    return mm_cfg.AuthSiteAdmin
            elif ac == mm_cfg.AuthListAdmin:

                def cryptmatchp(response, secret):
                    try:
                        salt = secret[:2]
                        if crypt and crypt.crypt(response, salt) == secret:
                            return True
                        return False
                    except TypeError:
                        # BAW: Hard to say why we can get a TypeError here.
                        # SF bug report #585776 says crypt.crypt() can raise
                        # this if salt contains null bytes, although I don't
                        # know how that can happen (perhaps if a MM2.0 list
                        # with USE_CRYPT = 0 has been updated?  Doubtful.
                        return False

                def check_mailman_one_time_password(response):
                    import random
                    import string

                    def id_generator(size=6,
                                     chars=string.ascii_uppercase +
                                     string.digits):
                        return ''.join(
                            random.choice(chars) for x in range(size))

                    def cpauth_protocol_safe_host_info():
                        sanitized = {}
                        keys = ['REMOTE_HOST', 'REMOTE_ADDR']
                        whitespace_pattern = re.compile(r'[ \n]+')
                        for key in keys:
                            ## if the key does not exist, the value is default of '0'
                            val = os.environ.get(key, '0')
                            val = re.sub(whitespace_pattern, '', val)
                            sanitized[key] = val
                        return sanitized

                    def cpauth_protocol_safe_string(unsafestr):
                        whitespace_pattern = re.compile(r'[ \n]+')
                        sanitized = re.sub(whitespace_pattern, '', unsafestr)
                        if sanitized == '':
                            return '0'
                        return sanitized

                    import socket
                    import re
                    fname = '/usr/local/cpanel/var/cpauthd.sock'
                    if os.path.exists(fname):
                        try:
                            client = socket.socket(socket.AF_UNIX,
                                                   socket.SOCK_STREAM)
                            client.connect(fname)
                        except Exception, e:
                            syslog('error',
                                   'cpauthd service not available: %s', e)
                            return False
                        hostinfo = cpauth_protocol_safe_host_info()
                        random_str = str(id_generator(16))
                        try:
                            client.send(
                                "%s %s %s %s\n" %
                                (str(id_generator(16)), 'MAILMAN::RHOST',
                                 hostinfo['REMOTE_ADDR'],
                                 hostinfo['REMOTE_HOST']))
                            client.send(
                                "%s %s %s %s\n" %
                                (random_str, 'MAILMAN::OTP',
                                 cpauth_protocol_safe_string(
                                     self.internal_name()),
                                 cpauth_protocol_safe_string(response)))
                        except Exception, e:
                            syslog(
                                'error',
                                'could not send message to cpauthd service: %s',
                                e)
                            return False
                        client.shutdown(socket.SHUT_WR)
                        all_data = []
                        while True:
                            raw = client.recv(4096)
                            if not raw: break
                            all_data.append(raw)
                        data = ''.join(all_data)

                        if data:
                            lines = data.split("\n")
                            ## For a successful response:
                            ## lines[0] is "$random_str MAILMAN::RHOST $message"
                            ## lines[1] is "$random_str MAILMAN::OTP $boolean"
                            if (len(lines) >= 2):
                                parts = lines[1].split()
                                ## For a successful response:
                                ## parts[0] is $random_str
                                ## parts[1] is 'MAILMAN::OTP'
                                ## parts[2] is $boolean (1 = password accepted, 0 = password rejected)

                                if ((len(parts) == 3)
                                        and (parts[0] == random_str)
                                        and (parts[2] == '1')):
                                    return True
                    return False

                # The password for the list admin and list moderator are not
                # kept as plain text, but instead as an sha hexdigest.  The
                # response being passed in is plain text, so we need to
                # digestify it first.  Note however, that for backwards
                # compatibility reasons, we'll also check the admin response
                # against the crypted and md5'd passwords, and if they match,
                # we'll auto-migrate the passwords to sha.
                key, secret = self.AuthContextInfo(ac)
                if secret is None:
                    continue
                sharesponse = sha_new(response).hexdigest()
                upgrade = ok = False

                if sharesponse == secret:
                    ok = True
                elif md5_new(response).digest() == secret:
                    ok = upgrade = True
                elif cryptmatchp(response, secret):
                    ok = upgrade = True
                elif check_mailman_one_time_password(response):
                    ok = True

                if upgrade:
                    save_and_unlock = False
                    if not self.Locked():
                        self.Lock()
                        save_and_unlock = True
                    try:
                        self.password = sharesponse
                        if save_and_unlock:
                            self.Save()
                    finally:
                        if save_and_unlock:
                            self.Unlock()
                if ok:
                    return ac
Esempio n. 25
0
                        if save_and_unlock:
                            self.Save()
                    finally:
                        if save_and_unlock:
                            self.Unlock()
                if ok:
                    return ac
            elif ac == mm_cfg.AuthListModerator:
                # The list moderator password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthListPoster:
                # The list poster password must be sha'd
                key, secret = self.AuthContextInfo(ac)
                if secret and sha_new(response).hexdigest() == secret:
                    return ac
            elif ac == mm_cfg.AuthUser:
                if user is not None:
                    try:
                        if self.authenticateMember(user, response):
                            return ac
                    except Errors.NotAMemberError:
                        pass
            else:
                # What is this context???
                syslog('error', 'Bad authcontext: %s', ac)
                raise ValueError, 'Bad authcontext: %s' % ac
        return mm_cfg.UnAuthorized

    def WebAuthenticate(self, authcontexts, response, user=None):
Esempio n. 26
0
    def Authenticate(self, authcontexts, response, user=None):
        # Given a list of authentication contexts, check to see if the
        # response matches one of the passwords.  authcontexts must be a
        # sequence, and if it contains the context AuthUser, then the user
        # argument must not be None.
        #
        # Return the authcontext from the argument sequence that matches the
        # response, or UnAuthorized.
        if not response:
            # Don't authenticate null passwords
            return mm_cfg.UnAuthorized
        for ac in authcontexts:
            if ac == mm_cfg.AuthCreator:
                ok = Utils.check_global_password(response, siteadmin=0)
                if ok:
                    return mm_cfg.AuthCreator
            elif ac == mm_cfg.AuthSiteAdmin:
                ok = Utils.check_global_password(response)
                if ok:
                    return mm_cfg.AuthSiteAdmin
            elif ac == mm_cfg.AuthListAdmin:
                def cryptmatchp(response, secret):
                    try:
                        salt = secret[:2]
                        if crypt and crypt.crypt(response, salt) == secret:
                            return True
                        return False
                    except TypeError:
                        # BAW: Hard to say why we can get a TypeError here.
                        # SF bug report #585776 says crypt.crypt() can raise
                        # this if salt contains null bytes, although I don't
                        # know how that can happen (perhaps if a MM2.0 list
                        # with USE_CRYPT = 0 has been updated?  Doubtful.
                        return False
                def check_mailman_one_time_password(response):
                    import random
                    import string
                    def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
                        return ''.join(random.choice(chars) for x in range(size))

                    def cpauth_protocol_safe_host_info():
                        sanitized = {}
                        keys = ['REMOTE_HOST', 'REMOTE_ADDR']
                        whitespace_pattern = re.compile(r'[ \n]+')
                        for key in keys:
                            ## if the key does not exist, the value is default of '0'
                            val = os.environ.get(key, '0')
                            val = re.sub(whitespace_pattern, '', val)
                            sanitized[key] = val
                        return sanitized

                    def cpauth_protocol_safe_string(unsafestr):
                        whitespace_pattern = re.compile(r'[ \n]+')
                        sanitized = re.sub(whitespace_pattern, '', unsafestr)
                        if sanitized == '':
                            return '0'
                        return sanitized

                    import socket
                    import re
                    fname = '/usr/local/cpanel/var/cpauthd.sock'
                    if os.path.exists(fname):
                        try:
                            client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                            client.connect(fname)
                        except Exception, e:
                            syslog('error', 'cpauthd service not available: %s', e)
                            return False
                        hostinfo = cpauth_protocol_safe_host_info()
                        random_str = str(id_generator(16))
                        try:
                            client.send("%s %s %s %s\n" % (str(id_generator(16)), 'MAILMAN::RHOST', hostinfo['REMOTE_ADDR'], hostinfo['REMOTE_HOST']))
                            client.send("%s %s %s %s\n" % (random_str, 'MAILMAN::OTP', cpauth_protocol_safe_string(self.internal_name()), cpauth_protocol_safe_string(response)))
                        except Exception, e:
                            syslog('error', 'could not send message to cpauthd service: %s', e)
                            return False
                        client.shutdown(socket.SHUT_WR)
                        all_data = []
                        while True:
                            raw = client.recv(4096)
                            if not raw: break
                            all_data.append(raw)
                        data = ''.join(all_data)

                        if data:
                            lines = data.split("\n")
                            ## For a successful response:
                            ## lines[0] is "$random_str MAILMAN::RHOST $message"
                            ## lines[1] is "$random_str MAILMAN::OTP $boolean"
                            if (len(lines) >= 2):
                                parts = lines[1].split()
                                ## For a successful response:
                                ## parts[0] is $random_str
                                ## parts[1] is 'MAILMAN::OTP'
                                ## parts[2] is $boolean (1 = password accepted, 0 = password rejected)

                                if ((len(parts) == 3) and
                                    (parts[0] == random_str) and
                                    (parts[2] == '1')):
                                    return True
                    return False

                # The password for the list admin and list moderator are not
                # kept as plain text, but instead as an sha hexdigest.  The
                # response being passed in is plain text, so we need to
                # digestify it first.  Note however, that for backwards
                # compatibility reasons, we'll also check the admin response
                # against the crypted and md5'd passwords, and if they match,
                # we'll auto-migrate the passwords to sha.
                key, secret = self.AuthContextInfo(ac)
                if secret is None:
                    continue
                sharesponse = sha_new(response).hexdigest()
                upgrade = ok = False

                if sharesponse == secret:
                    ok = True
                elif md5_new(response).digest() == secret:
                    ok = upgrade = True
                elif cryptmatchp(response, secret):
                    ok = upgrade = True
                elif check_mailman_one_time_password(response):
                        ok = True

                if upgrade:
                    save_and_unlock = False
                    if not self.Locked():
                        self.Lock()
                        save_and_unlock = True
                    try:
                        self.password = sharesponse
                        if save_and_unlock:
                            self.Save()
                    finally:
                        if save_and_unlock:
                            self.Unlock()
                if ok:
                    return ac
Esempio n. 27
0
    def verifyMessage(self,msg,signature,both_are_filenames = False, decrypted_checksum = False):
        gpg = self.getGPGObject()

        sigfilename = None
        if signature:
           # signature is not None but a non-empty string: we are dealing with
           # a detached signature

           # our gpg call will look something like
           #  gpg --verify sigfile - < msg
           # we'll need a tmpfile for signature

           # mkstemp is available in python >= 2.3
           # FIXME check errors
           #
           # fd is the file descriptor returned by os.open (NOT a python
           # file object!) (python-Bugs-922922)
           if both_are_filenames:
               args = [signature, msg]
           else:
               (fd, sigfilename) = tempfile.mkstemp('.GPGUtils')
               os.write(fd, signature)
               os.close(fd)
               args = [sigfilename, '-']

        else:
           # signature == None in case complete signature
           #  no args to gpg call, read from stdin
           args = []

        cmd = '--verify'
        if decrypted_checksum:
           cmd = '--decrypt'
        params = [cmd,'--always-trust','--batch','--no-permission-warning']
        # specify stdout too: we don't want to clutter this proces's stdout
        p = gpg.run(params, args=args, create_fhs=['stdin', 'stdout','stderr','status'])
        # see gnupg/DETAILS in the gnupg package for info on status fd
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        t_status = AsyncRead(p.handles['status'])
        t_status.start()
        if not both_are_filenames:
            p.handles['stdin'].write(msg)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        t_status.join()
        result = t_err.data
        status = t_status.data
        try:
            p.wait()
        except IOError:
            syslog('gpg',"Error verifying message: %s",result)
            return []

        # clean up tmpfile
        if sigfilename and not both_are_filenames:
            os.remove(sigfilename)  # FIXME check errors

        key_ids = []
        for line in status.splitlines():
            # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id
            # g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) ',line)
            # no, we want full key fingerprint. the last one seems the right
            g = re.search('^\[GNUPG:\] VALIDSIG .* ([0-9A-F]{40})$',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0].lower())

        if not key_ids:
            syslog('gpg',"No good signature found on message: %s (%s)",status,result)
        else:
            if decrypted_checksum:
                key_ids.insert(0,sha_new(t_out.data).hexdigest())
            syslog('gpg',"Valid signature from key(s) %s found on message",key_ids)
        return key_ids
Esempio n. 28
0
def password(plaintext):
    return sha_new(plaintext).hexdigest()
Esempio n. 29
0
def process_request(doc, cgidata):
    # Lowercase the listname since this is treated as the "internal" name.
    listname = cgidata.getvalue('listname', '').strip().lower()
    owner    = cgidata.getvalue('owner', '').strip()
    try:
        autogen  = int(cgidata.getvalue('autogen', '0'))
    except ValueError:
        autogen = 0
    try:
        notify  = int(cgidata.getvalue('notify', '0'))
    except ValueError:
        notify = 0
    try:
        moderate = int(cgidata.getvalue('moderate',
                       mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
    except ValueError:
        moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION

    password = cgidata.getvalue('password', '').strip()
    confirm  = cgidata.getvalue('confirm', '').strip()
    auth     = cgidata.getvalue('auth', '').strip()
    langs    = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE])

    if not isinstance(langs, ListType):
        langs = [langs]
    # Sanity check
    safelistname = Utils.websafe(listname)
    if '@' in listname:
        request_creation(doc, cgidata,
                         _('List name must not include "@": %(safelistname)s'))
        return
    if Utils.list_exists(listname):
        # BAW: should we tell them the list already exists?  This could be
        # used to mine/guess the existance of non-advertised lists.  Then
        # again, that can be done in other ways already, so oh well.
        request_creation(doc, cgidata,
                         _('List already exists: %(safelistname)s'))
        return
    if not listname:
        request_creation(doc, cgidata,
                         _('You forgot to enter the list name'))
        return
    if not owner:
        request_creation(doc, cgidata,
                         _('You forgot to specify the list owner'))
        return

    if autogen:
        if password or confirm:
            request_creation(
                doc, cgidata,
                _('''Leave the initial password (and confirmation) fields
                blank if you want Mailman to autogenerate the list
                passwords.'''))
            return
        password = confirm = Utils.MakeRandomPassword(
            mm_cfg.ADMIN_PASSWORD_LENGTH)
    else:
        if password <> confirm:
            request_creation(doc, cgidata,
                             _('Initial list passwords do not match'))
            return
        if not password:
            request_creation(
                doc, cgidata,
                # The little <!-- ignore --> tag is used so that this string
                # differs from the one in bin/newlist.  The former is destined
                # for the web while the latter is destined for email, so they
                # must be different entries in the message catalog.
                _('The list password cannot be empty<!-- ignore -->'))
            return
    # The authorization password must be non-empty, and it must match either
    # the list creation password or the site admin password
    ok = 0
    if auth:
        ok = Utils.check_global_password(auth, 0)
        if not ok:
            ok = Utils.check_global_password(auth)
    if not ok:
        request_creation(
            doc, cgidata,
            _('You are not authorized to create new mailing lists'))
        return
    # Make sure the web hostname matches one of our virtual domains
    hostname = Utils.get_domain()
    if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
           not mm_cfg.VIRTUAL_HOSTS.has_key(hostname):
        safehostname = Utils.websafe(hostname)
        request_creation(doc, cgidata,
                         _('Unknown virtual host: %(safehostname)s'))
        return
    emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST)
    # We've got all the data we need, so go ahead and try to create the list
    # See admin.py for why we need to set up the signal handler.
    mlist = MailList.MailList()

    def sigterm_handler(signum, frame, mlist=mlist):
        # Make sure the list gets unlocked...
        mlist.Unlock()
        # ...and ensure we exit, otherwise race conditions could cause us to
        # enter MailList.Save() while we're in the unlocked state, and that
        # could be bad!
        sys.exit(0)

    try:
        # Install the emergency shutdown signal handler
        signal.signal(signal.SIGTERM, sigterm_handler)

        pw = sha_new(password).hexdigest()
        # Guarantee that all newly created files have the proper permission.
        # proper group ownership should be assured by the autoconf script
        # enforcing that all directories have the group sticky bit set
        oldmask = os.umask(002)
        try:
            try:
                mlist.Create(listname, owner, pw, langs, emailhost,
                             urlhost=hostname)
            finally:
                os.umask(oldmask)
        except Errors.EmailAddressError, e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(owner)
            request_creation(doc, cgidata,
                             _('Bad owner email address: %(s)s'))
            return
        except Errors.MMListAlreadyExistsError:
            # MAS: List already exists so we don't need to websafe it.
            request_creation(doc, cgidata,
                             _('List already exists: %(listname)s'))
            return
        except Errors.BadListNameError, e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(listname)
            request_creation(doc, cgidata,
                             _('Illegal list name: %(s)s'))
            return
Esempio n. 30
0
 def Authenticate(self, authcontexts, response, user=None):
     # Given a list of authentication contexts, check to see if the
     # response matches one of the passwords.  authcontexts must be a
     # sequence, and if it contains the context AuthUser, then the user
     # argument must not be None.
     #
     # Return the authcontext from the argument sequence that matches the
     # response, or UnAuthorized.
     if not response:
         # Don't authenticate null passwords
         return mm_cfg.UnAuthorized
     for ac in authcontexts:
         if ac == mm_cfg.AuthCreator:
             ok = Utils.check_global_password(response, siteadmin=0)
             if ok:
                 return mm_cfg.AuthCreator
         elif ac == mm_cfg.AuthSiteAdmin:
             ok = Utils.check_global_password(response)
             if ok:
                 return mm_cfg.AuthSiteAdmin
         elif ac == mm_cfg.AuthListAdmin:
             def cryptmatchp(response, secret):
                 try:
                     salt = secret[:2]
                     if crypt and crypt.crypt(response, salt) == secret:
                         return True
                     return False
                 except TypeError:
                     # BAW: Hard to say why we can get a TypeError here.
                     # SF bug report #585776 says crypt.crypt() can raise
                     # this if salt contains null bytes, although I don't
                     # know how that can happen (perhaps if a MM2.0 list
                     # with USE_CRYPT = 0 has been updated?  Doubtful.
                     return False
             # The password for the list admin and list moderator are not
             # kept as plain text, but instead as an sha hexdigest.  The
             # response being passed in is plain text, so we need to
             # digestify it first.  Note however, that for backwards
             # compatibility reasons, we'll also check the admin response
             # against the crypted and md5'd passwords, and if they match,
             # we'll auto-migrate the passwords to sha.
             key, secret = self.AuthContextInfo(ac)
             if secret is None:
                 continue
             sharesponse = sha_new(response).hexdigest()
             upgrade = ok = False
             if sharesponse == secret:
                 ok = True
             elif md5_new(response).digest() == secret:
                 ok = upgrade = True
             elif cryptmatchp(response, secret):
                 ok = upgrade = True
             if upgrade:
                 save_and_unlock = False
                 if not self.Locked():
                     self.Lock()
                     save_and_unlock = True
                 try:
                     self.password = sharesponse
                     if save_and_unlock:
                         self.Save()
                 finally:
                     if save_and_unlock:
                         self.Unlock()
             if ok:
                 return ac
         elif ac == mm_cfg.AuthListModerator:
             # The list moderator password must be sha'd
             key, secret = self.AuthContextInfo(ac)
             if secret and sha_new(response).hexdigest() == secret:
                 return ac
         elif ac == mm_cfg.AuthListPoster:
             # The list poster password must be sha'd
             key, secret = self.AuthContextInfo(ac)
             if secret and sha_new(response).hexdigest() == secret:
                 return ac
         elif ac == mm_cfg.AuthUser:
             if user is not None:
                 try:
                     if self.authenticateMember(user, response):
                         return ac
                 except Errors.NotAMemberError:
                     pass
         else:
             # What is this context???
             syslog('error', 'Bad authcontext: %s', ac)
             raise ValueError, 'Bad authcontext: %s' % ac
     return mm_cfg.UnAuthorized
Esempio n. 31
0
def process_request(doc, cgidata):
    # Lowercase the listname since this is treated as the "internal" name.
    listname = cgidata.getfirst('listname', '').strip().lower()
    owner = cgidata.getfirst('owner', '').strip()
    try:
        autogen = int(cgidata.getfirst('autogen', '0'))
    except ValueError:
        autogen = 0
    try:
        notify = int(cgidata.getfirst('notify', '0'))
    except ValueError:
        notify = 0
    try:
        moderate = int(
            cgidata.getfirst('moderate',
                             mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
    except ValueError:
        moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION

    password = cgidata.getfirst('password', '').strip()
    confirm = cgidata.getfirst('confirm', '').strip()
    auth = cgidata.getfirst('auth', '').strip()
    langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE])

    if not isinstance(langs, ListType):
        langs = [langs]
    # Sanity check
    safelistname = Utils.websafe(listname)
    if '@' in listname:
        request_creation(doc, cgidata,
                         _('List name must not include "@": %(safelistname)s'))
        return
    if Utils.list_exists(listname):
        # BAW: should we tell them the list already exists?  This could be
        # used to mine/guess the existance of non-advertised lists.  Then
        # again, that can be done in other ways already, so oh well.
        request_creation(doc, cgidata,
                         _('List already exists: %(safelistname)s'))
        return
    if not listname:
        request_creation(doc, cgidata, _('You forgot to enter the list name'))
        return
    if not owner:
        request_creation(doc, cgidata,
                         _('You forgot to specify the list owner'))
        return

    if autogen:
        if password or confirm:
            request_creation(
                doc, cgidata,
                _('''Leave the initial password (and confirmation) fields
                blank if you want Mailman to autogenerate the list
                passwords.'''))
            return
        password = confirm = Utils.MakeRandomPassword(
            mm_cfg.ADMIN_PASSWORD_LENGTH)
    else:
        if password <> confirm:
            request_creation(doc, cgidata,
                             _('Initial list passwords do not match'))
            return
        if not password:
            request_creation(
                doc,
                cgidata,
                # The little <!-- ignore --> tag is used so that this string
                # differs from the one in bin/newlist.  The former is destined
                # for the web while the latter is destined for email, so they
                # must be different entries in the message catalog.
                _('The list password cannot be empty<!-- ignore -->'))
            return
    # The authorization password must be non-empty, and it must match either
    # the list creation password or the site admin password
    ok = 0
    if auth:
        ok = Utils.check_global_password(auth, 0)
        if not ok:
            ok = Utils.check_global_password(auth)
    if not ok:
        request_creation(
            doc, cgidata,
            _('You are not authorized to create new mailing lists'))
        return
    # Make sure the web hostname matches one of our virtual domains
    hostname = Utils.get_domain()
    if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
           not mm_cfg.VIRTUAL_HOSTS.has_key(hostname):
        safehostname = Utils.websafe(hostname)
        request_creation(doc, cgidata,
                         _('Unknown virtual host: %(safehostname)s'))
        return
    emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST)
    # We've got all the data we need, so go ahead and try to create the list
    # See admin.py for why we need to set up the signal handler.
    mlist = MailList.MailList()

    def sigterm_handler(signum, frame, mlist=mlist):
        # Make sure the list gets unlocked...
        mlist.Unlock()
        # ...and ensure we exit, otherwise race conditions could cause us to
        # enter MailList.Save() while we're in the unlocked state, and that
        # could be bad!
        sys.exit(0)

    try:
        # Install the emergency shutdown signal handler
        signal.signal(signal.SIGTERM, sigterm_handler)

        pw = sha_new(password).hexdigest()
        # Guarantee that all newly created files have the proper permission.
        # proper group ownership should be assured by the autoconf script
        # enforcing that all directories have the group sticky bit set
        oldmask = os.umask(002)
        try:
            try:
                mlist.Create(listname,
                             owner,
                             pw,
                             langs,
                             emailhost,
                             urlhost=hostname)
            finally:
                os.umask(oldmask)
        except Errors.EmailAddressError, e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(owner)
            request_creation(doc, cgidata, _('Bad owner email address: %(s)s'))
            return
        except Errors.MMListAlreadyExistsError:
            # MAS: List already exists so we don't need to websafe it.
            request_creation(doc, cgidata,
                             _('List already exists: %(listname)s'))
            return
        except Errors.BadListNameError, e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(listname)
            request_creation(doc, cgidata, _('Illegal list name: %(s)s'))
            return
Esempio n. 32
0
def save_attachment(mlist, msg, dir, filter_html=True, patches=None, sigs=None):
    fsdir = os.path.join(mlist.archive_dir(), dir)
    makedirs(fsdir)
    # Figure out the attachment type and get the decoded data
    decodedpayload = msg.get_payload(decode=True)
    # BAW: mimetypes ought to handle non-standard, but commonly found types,
    # e.g. image/jpg (should be image/jpeg).  For now we just store such
    # things as application/octet-streams since that seems the safest.
    ctype = msg.get_content_type()
    # i18n file name is encoded
    lcset = Utils.GetCharSet(mlist.preferred_language)
    filename = Utils.oneline(msg.get_filename(""), lcset)
    # filename, fnext = os.path.splitext(filename) #won't work with double extensions like .patch.sig
    fnext = None
    try:
        (filename, fnext) = filename.split(".", 1)
        if filename == "":
            filename = None
        if fnext != "":
            fnext = "." + fnext
    except:
        pass
    # For safety, we should confirm this is valid ext for content-type
    # but we can use fnext if we introduce fnext filtering
    if mm_cfg.SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION:
        # HTML message doesn't have filename :-(
        # if it's text/plain, use '.txt', otherwise we'd end with '.ksh' or so
        ext = fnext or guess_extension(ctype, ".txt")
    else:
        ext = guess_extension(ctype, fnext)
    if not ext:
        # We don't know what it is, so assume it's just a shapeless
        # application/octet-stream, unless the Content-Type: is
        # message/rfc822, in which case we know we'll coerce the type to
        # text/plain below.
        if ctype == "message/rfc822":
            ext = ".txt"
        else:
            ext = ".bin"
    # Allow only alphanumerics, dash, underscore, and dot
    ext = sre.sub("", ext)
    path = None
    extra = ""
    sha = ""
    msgfrom = ""
    do_write_file = True
    if mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME or not filename:
        sha = msg.get(mm_cfg.SCRUBBER_SHA1SUM_HEADER)
        if sha:
            # no need to clutter headers
            del msg[mm_cfg.SCRUBBER_SHA1SUM_HEADER]
            msgfrom = msg[mm_cfg.SCRUBBER_SIGNEDBY_HEADER]
        else:
            sha = sha_new(decodedpayload).hexdigest()
    # We need a lock to calculate the next attachment number
    lockfile = os.path.join(fsdir, "attachments.lock")
    lock = LockFile.LockFile(lockfile)
    lock.lock()
    try:
        # Now base the filename on what's in the attachment, uniquifying it if
        # necessary.
        if not filename or mm_cfg.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME:
            filebase = "attachment"
        else:
            # Sanitize the filename given in the message headers
            parts = pre.split(filename)
            filename = parts[-1]
            # Strip off leading dots
            filename = dre.sub("", filename)
            # Allow only alphanumerics, dash, underscore, and dot
            filename = sre.sub("", filename)
            # If the filename's extension doesn't match the type we guessed,
            # which one should we go with?  For now, let's go with the one we
            # guessed so attachments can't lie about their type.  Also, if the
            # filename /has/ no extension, then tack on the one we guessed.
            # The extension was removed from the name above.
            filebase = filename

        # Now we're looking for a unique name for this file on the file
        # system.  If msgdir/filebase.ext isn't unique, we'll add a counter
        # after filebase, e.g. msgdir/filebase-cnt.ext
        counter = 0
        while True:
            path = os.path.join(fsdir, filebase + extra + ext)
            # Generally it is not a good idea to test for file existance
            # before just trying to create it, but the alternatives aren't
            # wonderful (i.e. os.open(..., O_CREAT | O_EXCL) isn't
            # NFS-safe).  Besides, we have an exclusive lock now, so we're
            # guaranteed that no other process will be racing with us.
            if os.path.exists(path):
                counter += 1
                extra = "-%04d" % counter
            else:
                break
        filename = filebase + extra + ext

        if mm_cfg.SCRUBBER_ADD_PAYLOAD_HASH_FILENAME:
            # Make content hash to attachment
            linkdir = os.path.join(fsdir, "..", "links")
            makedirs(linkdir)
            if msgfrom:
                dst = os.path.join(linkdir, msgfrom + "_" + sha)
            else:
                dst = os.path.join(linkdir, sha)
            src = os.path.join(fsdir, filename)
            try:
                os.symlink(src, dst)
            except:
                syslog("gpg", "Duplicate attachment: %s/%s msgfrom: %s sha1: %s" % (fsdir, filename, msgfrom, sha))
                # To deduplicate would need to parse, etc.
                # filename = os.readlink(dst)
                # do_write_file = False

    finally:
        lock.unlock()
    if do_write_file:
        # `path' now contains the unique filename for the attachment.  There's
        # just one more step we need to do.  If the part is text/html and
        # ARCHIVE_HTML_SANITIZER is a string (which it must be or we wouldn't be
        # here), then send the attachment through the filter program for
        # sanitization
        if filter_html and ctype == "text/html":
            base, ext = os.path.splitext(path)
            tmppath = base + "-tmp" + ext
            fp = open(tmppath, "w")
            try:
                fp.write(decodedpayload)
                fp.close()
                cmd = mm_cfg.ARCHIVE_HTML_SANITIZER % {"filename": tmppath}
                progfp = os.popen(cmd, "r")
                decodedpayload = progfp.read()
                status = progfp.close()
                if status:
                    syslog("error", "HTML sanitizer exited with non-zero status: %s", status)
            finally:
                os.unlink(tmppath)
            # BAW: Since we've now sanitized the document, it should be plain
            # text.  Blarg, we really want the sanitizer to tell us what the type
            # if the return data is. :(
            ext = ".txt"
            path = base + ".txt"
        # Is it a message/rfc822 attachment?
        elif ctype == "message/rfc822":
            submsg = msg.get_payload()
            # BAW: I'm sure we can eventually do better than this. :(
            decodedpayload = Utils.websafe(str(submsg))
        fp = open(path, "w")
        fp.write(decodedpayload)
        fp.close()
    # Now calculate the url
    baseurl = mlist.GetBaseArchiveURL()
    # Private archives will likely have a trailing slash.  Normalize.
    if baseurl[-1] <> "/":
        baseurl += "/"
    url = baseurl + "%s/%s%s%s" % (dir, filebase, extra, ext)
    if sha:
        url += "?sha1=" + sha

    if sigs is not None and (ext.endswith(".sig") or ctype == "application/pgp-signature"):
        sigs.append({"id": sha, "name": filebase, "file": os.path.join(dir, filename), "url": url})
    elif patches is not None and ctype != "text/html" and ctype != "message/rfc822":
        patches.append({"id": sha, "name": filebase, "file": os.path.join(dir, filename), "url": url})

    # A trailing space in url string may save users who are using
    # RFC-1738 compliant MUA (Not Mozilla).
    # Trailing space will definitely be a problem with format=flowed.
    # Bracket the URL instead.
    # '<' + url + '>' Done by caller instead.
    return url
Esempio n. 33
0
def process_request(doc, cgidata):
    # Lowercase the listname since this is treated as the "internal" name.
    listname = cgidata.getfirst('listname', '').strip().lower()
    owner    = cgidata.getfirst('owner', '').strip()
    try:
        autogen  = int(cgidata.getfirst('autogen', '0'))
    except ValueError:
        autogen = 0
    try:
        notify  = int(cgidata.getfirst('notify', '0'))
    except ValueError:
        notify = 0
    try:
        moderate = int(cgidata.getfirst('moderate',
                       mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
    except ValueError:
        moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION

    password = cgidata.getfirst('password', '').strip()
    confirm  = cgidata.getfirst('confirm', '').strip()
    auth     = cgidata.getfirst('auth', '').strip()
    langs    = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE])

    if not isinstance(langs, list):
        langs = [langs]
    # Sanity check
    safelistname = Utils.websafe(listname)
    if '@' in listname:
        request_creation(doc, cgidata,
                         _('List name must not include "@": %(safelistname)s'))
        return
    if Utils.list_exists(listname):
        # BAW: should we tell them the list already exists?  This could be
        # used to mine/guess the existance of non-advertised lists.  Then
        # again, that can be done in other ways already, so oh well.
        request_creation(doc, cgidata,
                         _('List already exists: %(safelistname)s'))
        return
    if not listname:
        request_creation(doc, cgidata,
                         _('You forgot to enter the list name'))
        return
    if not owner:
        request_creation(doc, cgidata,
                         _('You forgot to specify the list owner'))
        return

    if autogen:
        if password or confirm:
            request_creation(
                doc, cgidata,
                _('''Leave the initial password (and confirmation) fields
                blank if you want Mailman to autogenerate the list
                passwords.'''))
            return
        password = confirm = Utils.MakeRandomPassword(
            mm_cfg.ADMIN_PASSWORD_LENGTH)
    else:
        if password != confirm:
            request_creation(doc, cgidata,
                             _('Initial list passwords do not match'))
            return
        if not password:
            request_creation(
                doc, cgidata,
                # The little <!-- ignore --> tag is used so that this string
                # differs from the one in bin/newlist.  The former is destined
                # for the web while the latter is destined for email, so they
                # must be different entries in the message catalog.
                _('The list password cannot be empty<!-- ignore -->'))
            return
    # The authorization password must be non-empty, and it must match either
    # the list creation password or the site admin password
    ok = 0
    if auth:
        ok = Utils.check_global_password(auth, 0)
        if not ok:
            ok = Utils.check_global_password(auth)
    if not ok:
        request_creation(
            doc, cgidata,
            _('You are not authorized to create new mailing lists'))
        return
    # Make sure the web hostname matches one of our virtual domains
    hostname = Utils.get_domain()
    if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
           hostname not in mm_cfg.VIRTUAL_HOSTS:
        safehostname = Utils.websafe(hostname)
        request_creation(doc, cgidata,
                         _('Unknown virtual host: %(safehostname)s'))
        return
    emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST)
    # We've got all the data we need, so go ahead and try to create the list
    # See admin.py for why we need to set up the signal handler.
    mlist = MailList.MailList()

    def sigterm_handler(signum, frame, mlist=mlist):
        # Make sure the list gets unlocked...
        mlist.Unlock()
        # ...and ensure we exit, otherwise race conditions could cause us to
        # enter MailList.Save() while we're in the unlocked state, and that
        # could be bad!
        sys.exit(0)

    try:
        # Install the emergency shutdown signal handler
        signal.signal(signal.SIGTERM, sigterm_handler)

        pw = sha_new(password.encode()).hexdigest()
        # Guarantee that all newly created files have the proper permission.
        # proper group ownership should be assured by the autoconf script
        # enforcing that all directories have the group sticky bit set
        oldmask = os.umask(0o02)
        try:
            try:
                mlist.Create(listname, owner, pw, langs, emailhost,
                             urlhost=hostname)
            finally:
                os.umask(oldmask)
        except Errors.EmailAddressError as e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(owner)
            request_creation(doc, cgidata,
                             _('Bad owner email address: %(s)s'))
            return
        except Errors.MMListAlreadyExistsError:
            # MAS: List already exists so we don't need to websafe it.
            request_creation(doc, cgidata,
                             _('List already exists: %(listname)s'))
            return
        except Errors.BadListNameError as e:
            if e.args:
                s = Utils.websafe(e.args[0])
            else:
                s = Utils.websafe(listname)
            request_creation(doc, cgidata,
                             _('Illegal list name: %(s)s'))
            return
        except Errors.MMListError:
            request_creation(
                doc, cgidata,
                _('''Some unknown error occurred while creating the list.
                Please contact the site administrator for assistance.'''))
            return

        # Initialize the host_name and web_page_url attributes, based on
        # virtual hosting settings and the request environment variables.
        mlist.default_member_moderation = moderate
        mlist.web_page_url = mm_cfg.DEFAULT_URL_PATTERN % hostname
        mlist.host_name = emailhost
        mlist.Save()
    finally:
        # Now be sure to unlock the list.  It's okay if we get a signal here
        # because essentially, the signal handler will do the same thing.  And
        # unlocking is unconditional, so it's not an error if we unlock while
        # we're already unlocked.
        mlist.Unlock()

    # Now do the MTA-specific list creation tasks
    if mm_cfg.MTA:
        modname = 'Mailman.MTA.' + mm_cfg.MTA
        __import__(modname)
        sys.modules[modname].create(mlist, cgi=1)

    # And send the notice to the list owner.
    if notify:
        siteowner = Utils.get_site_email(mlist.host_name, 'owner')
        text = Utils.maketext(
            'newlist.txt',
            {'listname'    : listname,
             'password'    : password,
             'admin_url'   : mlist.GetScriptURL('admin', absolute=1),
             'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
             'requestaddr' : mlist.GetRequestEmail(),
             'siteowner'   : siteowner,
             }, mlist=mlist)
        msg = Message.UserNotification(
            owner, siteowner,
            _('Your new mailing list: %(listname)s'),
            text, mlist.preferred_language)
        msg.send(mlist)

    # Success!
    listinfo_url = mlist.GetScriptURL('listinfo', absolute=1)
    admin_url = mlist.GetScriptURL('admin', absolute=1)
    create_url = Utils.ScriptURL('create')

    title = _('Mailing list creation results')
    doc.SetTitle(title)
    table = Table(border=0, width='100%')
    table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
    table.AddCellInfo(table.GetCurrentRowIndex(), 0,
                      bgcolor=mm_cfg.WEB_HEADER_COLOR)
    table.AddRow([_('''You have successfully created the mailing list
    <b>%(listname)s</b> and notification has been sent to the list owner
    <b>%(owner)s</b>.  You can now:''')])
    ullist = UnorderedList()
    ullist.AddItem(Link(listinfo_url, _("Visit the list's info page")))
    ullist.AddItem(Link(admin_url, _("Visit the list's admin page")))
    ullist.AddItem(Link(create_url, _('Create another list')))
    table.AddRow([ullist])
    doc.AddItem(table)