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 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 # 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