# Author: Zhang Huangbin import sys import ldap import web from libs import iredutils from libs.ldaplib import core, attrs, iredldif, ldaputils, deltree, connUtils, decorators session = web.config.get('_session') class Maillist(core.LDAPWrap): def __del__(self): try: self.conn.unbind() except: pass @decorators.require_domain_access def listAccounts(self, domain, searchAttrs=attrs.MAILLIST_SEARCH_ATTRS,): self.domain = str(domain) self.domainDN = ldaputils.convKeywordToDN(self.domain, accountType='domain') # Search users under domain. try: self.maillists = self.conn.search_s( attrs.DN_BETWEEN_MAILLIST_AND_DOMAIN + self.domainDN, ldap.SCOPE_SUBTREE, "(objectClass=mailList)", searchAttrs, ) connutils = connUtils.Utils() connutils.updateAttrSingleValue(self.domainDN, 'domainCurrentListNumber', len(self.maillists)) return (True, self.maillists) except ldap.NO_SUCH_OBJECT: self.conn.add_s( attrs.DN_BETWEEN_MAILLIST_AND_DOMAIN + self.domainDN, iredldif.ldif_group(attrs.GROUP_GROUPS), ) return (True, []) except ldap.SIZELIMIT_EXCEEDED: return (False, 'EXCEEDED_LDAP_SERVER_SIZELIMIT') except Exception, e: return (False, ldaputils.getExceptionDesc(e)) @decorators.require_domain_access def add(self, domain, data): self.domain = web.safestr(domain).lower() if not iredutils.isDomain(self.domain): return (False, 'INVALID_DOMAIN_NAME') self.listname = web.safestr(data.get('listname')) self.mail = self.listname + '@' + self.domain if not iredutils.isEmail(self.mail): return (False, 'INVALID_USERNAME') self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') # Check whether account exist or not. connutils = connUtils.Utils() if connutils.isAccountExists(domain=self.domain, filter='(|(mail=%s)(shadowAddress=%s))' % (self.mail, self.mail)): return (False, 'ALREADY_EXISTS') ldif = iredldif.ldif_maillist(self.mail, cn=data.get('cn')) try: self.conn.add_s(self.dn, ldif) web.logger(msg="Create mail list: %s." % (self.mail), domain=self.domain, event='create',) return (True,) except ldap.ALREADY_EXISTS: return (False, 'ALREADY_EXISTS') except ldap.LDAPError, e: return (False, ldaputils.getExceptionDesc(e)) # Get profile of mail list. @decorators.require_domain_access def profile(self, domain, mail, profile_type='general',): self.profile_type = web.safestr(profile_type) self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[-1] if self.domain != domain: return web.seeother('/domains?msg=PERMISSION_DENIED') if not self.mail.endswith('@' + self.domain): return web.seeother('/domains?msg=PERMISSION_DENIED') self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') try: self.maillist_profile = self.conn.search_s( self.dn, ldap.SCOPE_BASE, '(&(objectClass=mailList)(mail=%s))' % self.mail, attrs.MAILLIST_ATTRS_ALL, ) return (True, self.maillist_profile) except Exception, e: return (False, ldaputils.getExceptionDesc(e)) # Update mail list profile. @decorators.require_domain_access def update(self, profile_type, mail, data,): self.profile_type = web.safestr(profile_type) self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[1] self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') connutils = connUtils.Utils() mod_attrs = [] if self.profile_type == 'general': # Get cn. cn = data.get('cn', None) mod_attrs += ldaputils.getSingleModAttr(attr='cn', value=cn, default=self.mail.split('@')[0]) # Get accountStatus. """ if 'accountStatus' in data.keys(): accountStatus = 'active' # Force to update enabledService. for s in ['mail', 'deliver',]: result = connutils.addOrDelAttrValue( dn=self.dn, attr='enabledService', value=s, action='add', ) if result[0] is False: return result else: accountStatus = 'disabled' mod_attrs += [ (ldap.MOD_REPLACE, 'accountStatus', accountStatus) ] """ # Update access policy. self.policyChanged = False self.oldAccessPolicy = web.safestr(data.get('oldAccessPolicy', None)) self.accessPolicy = web.safestr(data.get('accessPolicy', None)) if self.oldAccessPolicy != self.accessPolicy and \ self.accessPolicy in attrs.MAILLIST_ACCESS_POLICIES: mod_attrs += [ (ldap.MOD_REPLACE, 'accessPolicy', self.accessPolicy) ] self.policyChanged = True # Get enabledService=displayedInGlobalAddressBook, and update # 'hasMember'. if 'displayedInGlobalAddressBook' in data.keys(): mod_type = 'add' mod_attrs += [ (ldap.MOD_REPLACE, 'hasMember', 'yes') ] else: mod_type = 'delete' mod_attrs += [ (ldap.MOD_REPLACE, 'hasMember', None) ] result = connutils.addOrDelAttrValue( dn=self.dn, attr='enabledService', value='displayedInGlobalAddressBook', action=mod_type, ) if result[0] is False: return result try: self.conn.modify_s(self.dn, mod_attrs) if self.policyChanged: web.logger(msg="Deliver restriction of %s changed: %s -> %s" % (self.mail, self.oldAccessPolicy, self.accessPolicy), domain=self.domain, username=self.mail, event='modify', ) return (True,) except Exception, e: return (False, ldaputils.getExceptionDesc(e)) # Delete mail lists. @decorators.require_domain_access def delete(self, domain, mails,): if mails is None or len(mails) == 0: return False self.domain = str(domain) self.mails = [str(v) for v in mails if iredutils.isEmail(v) and str(v).endswith('@'+self.domain)] result = {} for mail in self.mails: self.mail = web.safestr(mail) self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') self.extdn = ldaputils.convKeywordToDN(self.mail, accountType='maillistExternal') try: # Delete dn under @attrs.DN_BETWEEN_MAILLIST_AND_DOMAIN. deltree.DelTree( self.conn, self.dn, ldap.SCOPE_SUBTREE ) try: deltree.DelTree( self.conn, self.extdn, ldap.SCOPE_SUBTREE ) except ldap.NO_SUCH_OBJECT: pass except Exception, e: result[self.mail] = str(e) connutils = connUtils.Utils() # Delete 'memberOfGroup' attribute in member dn. # Get group members. result_curMembers = self.members(domain=self.domain, mail=self.mail,) if result_curMembers[0] is True: for i in result_curMembers[1]: # Delete 'memberOfGroup' attribute. connutils.addOrDelAttrValue( dn=i[0], attr='memberOfGroup', value=self.mail, action='delete', ) web.logger(msg="Delete mail list: %s." % (self.mail), domain=self.domain, event='delete',) except ldap.LDAPError, e: result[self.mail] = str(e) if result == {}: return (True,) else: return (False, ldaputils.getExceptionDesc(result)) # Get members of mail list. @decorators.require_domain_access def members(self, domain, mail,): self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[-1] if self.domain != domain: return web.seeother('/domains?msg=PERMISSION_DENIED') if not self.mail.endswith('@' + self.domain): return web.seeother('/domains?msg=PERMISSION_DENIED') self.domainDN = ldaputils.convKeywordToDN(self.domain, accountType='domain') # Pre-defined. msg = '' self.internalMembers = [] # LDAP query result set. self.externalMembers = [] # List of email addresses. # Variables used to search internal members. self.searchDNOfInternal = attrs.DN_BETWEEN_USER_AND_DOMAIN + self.domainDN self.searchScopeOfInternal = ldap.SCOPE_SUBTREE self.searchAttrsOfInternal = attrs.USER_ATTRS_ALL self.searchFilterOfInternal = '(&(objectClass=mailUser)(memberOfGroup=%s))' % self.mail # Variables used to search external members. self.searchDNOfExternal = 'memberOfGroup=%s,%s%s' % ( self.mail, attrs.DN_BETWEEN_MAILLIST_EXTERNAL_AND_DOMAIN, self.domainDN, ) self.searchScopeOfExternal = ldap.SCOPE_BASE self.searchAttrsOfExternal = ['mail'] self.searchFilterOfExternal = '(&(objectClass=mailExternalUser)(memberOfGroup=%s))' % self.mail # Search internal members. try: self.internalMembers = self.conn.search_s( self.searchDNOfInternal, self.searchScopeOfInternal, self.searchFilterOfInternal, self.searchAttrsOfInternal, ) except Exception, e: msg += ldaputils.getExceptionDesc(e) # Search external members try: self.resultOfExternalMembers = self.conn.search_s( self.searchDNOfExternal, self.searchScopeOfExternal, self.searchFilterOfExternal, self.searchAttrsOfExternal, ) if len(self.resultOfExternalMembers) == 1: try: self.externalMembers = self.resultOfExternalMembers[0][1].get('mail', []) except Exception, e: msg += ldaputils.getExceptionDesc(e) except ldap.NO_SUCH_OBJECT: ldif = iredldif.ldif_mailExternalUser(self.mail, ) try: self.conn.add_s(self.searchDNOfExternal, ldif) except Exception, e: return (False, ldaputils.getExceptionDesc(e)) except Exception, e: msg += ldaputils.getExceptionDesc(e) if msg == '': return (True, self.internalMembers, self.externalMembers) else: return (False, msg) # Get current members from mail list or assign members to mail list. @decorators.require_domain_access def assignOrRemoveInternalMembers(self, domain, mail, members, action): self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[-1] if self.domain != domain: return web.seeother('/domains?msg=PERMISSION_DENIED') if not self.mail.endswith('@' + self.domain): return web.seeother('/domains?msg=PERMISSION_DENIED') self.members = members self.action = web.safestr(action) # Get action: assign or remove. if self.action == 'assign' or self.action == 'add': self.msg = 'msg=MEMBER_ASSIGNED_SUCCESS' elif self.action == 'delete': self.msg = 'msg=MEMBER_REMOVED_SUCCESS' # Assign or remove. msg = {} connutils = connUtils.Utils() for member in self.members: if iredutils.isEmail(member): self.dn = ldaputils.convKeywordToDN(member, accountType='user') result = connutils.addOrDelAttrValue( dn=self.dn, attr='memberOfGroup', value=self.mail, action=self.action, ) if result[0] is False: msg[member] = result[1] if msg == {}: return (True, ) else: return (False, msg) # Update external members. @decorators.require_domain_access def assignOrRemoveExternalMembers(self, domain, mail, members, action,): self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[-1] if self.domain != domain: return web.seeother('/domains?msg=PERMISSION_DENIED') if not self.mail.endswith('@' + self.domain): return web.seeother('/domains?msg=PERMISSION_DENIED') self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillistExternal') self.members = members self.action = web.safestr(action) # Assign or remove. msg = {} connutils = connUtils.Utils() for member in self.members: if iredutils.isEmail(member): result = connutils.addOrDelAttrValue( dn=self.dn, attr='mail', value=member, action=self.action, ) if result[0] is False: msg[member] = result[1] if msg == {}: return (True, ) else: return (False, msg) # Get moderators of mail list. @decorators.require_domain_access def moderators(self, domain, mail,): # Return ldap query result of internal members, list of external members. self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[-1] if self.domain != domain: return web.seeother('/domains?msg=PERMISSION_DENIED') if not self.mail.endswith('@' + self.domain): return web.seeother('/domains?msg=PERMISSION_DENIED') # Get mail list dn. self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') # Get domain dn. self.domaindn = ldaputils.convKeywordToDN(self.domain, accountType='domain') # Search mail list, get addresses of moderators first. try: # Search mail list. self.listAllowedUsers = self.conn.search_s( self.dn, ldap.SCOPE_BASE, '(&(objectClass=mailList)(mail=%s))' % self.mail, ['listAllowedUser'], ) except Exception, e: return (False, ldaputils.getExceptionDesc(e)) # It should be only one result. if len(self.listAllowedUsers) != 1: return (True, [], [],) # No moderators. if len(self.listAllowedUsers[0][1]) <= 0: return (True, [], [],) # Search domain dn to get detail of internal members. allModerators = self.listAllowedUsers[0][1].get('listAllowedUser', []) # Get mail addr of internal members. internalModerators = [v for v in allModerators if v.endswith('@'+self.domain)] externalModerators = [v for v in allModerators if not v.endswith('@'+self.domain)] # Generate new search filter to get detail profile of internal members. self.filter = '(&(objectClass=mailUser)(|' for m in internalModerators: self.filter += '(mail=%s)' % m self.filter += '))' try: self.profile_of_internal_members= self.conn.search_s( self.domaindn, ldap.SCOPE_SUBTREE, self.filter, attrs.USER_ATTRS_ALL, ) return (True, self.profile_of_internal_members, externalModerators) except Exception, e: return (False, ldaputils.getExceptionDesc(e)) # Get current moderators from mail list or assign moderators to mail list. @decorators.require_domain_access def assignOrRemoveModerators(self, domain, mail, moderators, action,): self.mail = web.safestr(mail) self.domain = self.mail.split('@', 1)[-1] if self.domain != domain: return web.seeother('/domains?msg=PERMISSION_DENIED') if not self.mail.endswith('@' + self.domain): return web.seeother('/domains?msg=PERMISSION_DENIED') self.moderators = moderators self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') self.action = web.safestr(action) # Get action: assign or remove. #if 'add' in data.keys(): # self.action = 'add' # self.msg = 'MODERATOR_ASSIGNED_SUCCESS' #elif 'delete' in data.keys(): # self.action = 'delete' # self.msg = 'MODERATOR_REMOVED_SUCCESS' # Assign or remove. msg = {} connutils = connUtils.Utils() for moderator in self.moderators: result = connutils.addOrDelAttrValue( dn=self.dn, attr='listAllowedUser', value=moderator, action=self.action, ) if result[0] is False: msg[moderator] = result[1] if msg == {}: return (True, ) else: return (False, msg) # Enable or disable mail list. @decorators.require_domain_access def enableOrDisableAccount(self, domain, mails, action, attr='accountStatus',): if mails is None or len(mails) == 0: return (False, ldaputils.getExceptionDesc('NO_ACCOUNT_SELECTED')) self.mails = [str(v) for v in mails if iredutils.isEmail(v) and str(v).endswith('@'+str(domain)) ] result = {} connutils = connUtils.Utils() for mail in self.mails: self.mail = web.safestr(mail) if not iredutils.isEmail(self.mail): continue self.domain = self.mail.split('@')[-1] self.dn = ldaputils.convKeywordToDN(self.mail, accountType='maillist') try: connutils.enableOrDisableAccount( domain=self.domain, account=self.mail, dn=self.dn, action=web.safestr(action).strip().lower(), accountTypeInLogger='maillist', ) except ldap.LDAPError, e: result[self.mail] = str(e) if result == {}: return (True,) else: return (False, ldaputils.getExceptionDesc(result))