def test_list_admin_oldstyle_unauth(self): eq = self.assertEqual mlist = self._mlist mlist.password = md5_new('ssSSss').digest() eq(mlist.Authenticate( [mm_cfg.AuthListAdmin], 'xxxxxx'), mm_cfg.UnAuthorized) eq(mlist.password, md5_new('ssSSss').digest()) # Test crypt upgrades if crypt is supported if crypt: mlist.password = crypted = crypt.crypt('rrRRrr', 'zc') eq(self._mlist.Authenticate( [mm_cfg.AuthListAdmin], 'xxxxxx'), mm_cfg.UnAuthorized) eq(mlist.password, crypted)
def addNewMember(self, member, **kws): # assert self.__mlist.Locked() # Make sure this address isn't already a member if self.isMember(member): raise Errors.MMAlreadyAMember, member # Parse the keywords digest = 0 password = Utils.MakeRandomPassword() language = self.__mlist.preferred_language realname = None if kws.has_key('digest'): digest = kws['digest'] del kws['digest'] if kws.has_key('password'): password = kws['password'] del kws['password'] if kws.has_key('language'): language = kws['language'] del kws['language'] if kws.has_key('realname'): realname = kws['realname'] del kws['realname'] # Assert that no other keywords are present if kws: raise ValueError, kws.keys() # If the localpart has uppercase letters in it, then the value in the # members (or digest_members) dict is the case preserved address. # Otherwise the value is 0. Note that the case of the domain part is # of course ignored. if Utils.LCDomain(member) == member.lower(): value = 0 else: value = member member = member.lower() if digest: digest = 'Y' else: digest = 'N' # All we need to do here is add the address. # and Set the member's default set of options if self.__mlist.new_member_options: options = self.__mlist.new_member_options else: options = 0 query = "INSERT INTO %s " \ + "(address, user_options, password, lang, " \ + "digest, delivery_status,listname) values " \ + "('%s',%s,'%s','%s','%s','%s','%s')" query = query %( self._table, self.escape(member), options, md5_new(password).hexdigest(), language, digest, MemberAdaptor.ENABLED,self.__mlist.internal_name()) if mm_cfg.MYSQL_MEMBER_DB_VERBOSE: syslog('mysql',query) mm_cfg.cursor.execute(query) mm_cfg.connection.commit() if realname: self.setMemberName(member, realname)
def test_list_admin_upgrade(self): eq = self.assertEqual mlist = self._mlist mlist.password = md5_new('ssSSss'.encode()).digest() eq(mlist.Authenticate([mm_cfg.AuthListAdmin], 'ssSSss'), mm_cfg.AuthListAdmin) eq(mlist.password, password('ssSSss')) # Test crypt upgrades if crypt is supported if crypt: mlist.password = crypt.crypt('rrRRrr', 'zc') eq(self._mlist.Authenticate([mm_cfg.AuthListAdmin], 'rrRRrr'), mm_cfg.AuthListAdmin) eq(mlist.password, password('rrRRrr'))
def test_list_admin_upgrade(self): eq = self.assertEqual mlist = self._mlist mlist.password = md5_new('ssSSss').digest() eq(mlist.Authenticate( [mm_cfg.AuthListAdmin], 'ssSSss'), mm_cfg.AuthListAdmin) eq(mlist.password, password('ssSSss')) # Test crypt upgrades if crypt is supported if crypt: mlist.password = crypt.crypt('rrRRrr', 'zc') eq(self._mlist.Authenticate( [mm_cfg.AuthListAdmin], 'rrRRrr'), mm_cfg.AuthListAdmin) eq(mlist.password, password('rrRRrr'))
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
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 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
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
def setMemberPassword(self, member, password): # assert self.__mlist.Locked() self.update_on("password", md5_new(password).hexdigest(), member)
def addNewMember(self, member, **kws): # assert self.__mlist.Locked() # Make sure this address isn't already a member if self.isMember(member): raise Errors.MMAlreadyAMember, member # Parse the keywords digest = 0 password = Utils.MakeRandomPassword() language = self.__mlist.preferred_language realname = None if kws.has_key("digest"): digest = kws["digest"] del kws["digest"] if kws.has_key("password"): password = kws["password"] del kws["password"] if kws.has_key("language"): language = kws["language"] del kws["language"] if kws.has_key("realname"): realname = kws["realname"] del kws["realname"] # Assert that no other keywords are present if kws: raise ValueError, kws.keys() # If the localpart has uppercase letters in it, then the value in the # members (or digest_members) dict is the case preserved address. # Otherwise the value is 0. Note that the case of the domain part is # of course ignored. if Utils.LCDomain(member) == member.lower(): value = 0 else: value = member member = member.lower() if digest: digest = "Y" else: digest = "N" # All we need to do here is add the address. # and Set the member's default set of options if self.__mlist.new_member_options: options = self.__mlist.new_member_options else: options = 0 query = ( "INSERT INTO %s " + "(address, user_options, password, lang, " + "digest, delivery_status,listname) values " + "('%s',%s,'%s','%s','%s','%s','%s')" ) query = query % ( self._table, self.escape(member), options, md5_new(password).hexdigest(), language, digest, MemberAdaptor.ENABLED, self.__mlist.internal_name(), ) if mm_cfg.MYSQL_MEMBER_DB_VERBOSE: syslog("mysql", query) mm_cfg.cursor.execute(query) mm_cfg.connection.commit() if realname: self.setMemberName(member, realname)
def authenticateMember(self, member, response): secret = self.getMemberPassword(member) if secret == md5_new(response).hexdigest(): return secret return 0
def setMemberPassword(self, member, password): # assert self.__mlist.Locked() self.update_on('password', md5_new(password).hexdigest(), member)