# Author: Zhang Huangbin import web from libs import iredutils, form_utils from libs.logger import log_traceback, log_activity from libs.sqllib import SQLWrap, decorators from libs.sqllib import general as sql_lib_general from libs.sqllib import domain as sql_lib_domain from libs import mlmmj import settings session = web.config.get('_session') def __is_mlid_exists(mlid, conn=None): """Return True if mailing list id exists.""" mlid = str(mlid).lower() if not conn: _wrap = SQLWrap() conn = _wrap.conn try: qr = conn.select('maillists', vars={'mlid': mlid}, what='mlid', where='mlid=$mlid', limit=1) if qr: return True else: return False except: return False def __get_new_mlid(conn=None): mlid = mlmmj.generate_mlid() if not conn: _wrap = SQLWrap() conn = _wrap.conn _counter = 0 while True: # Try 20 times. if _counter >= 20: raise ValueError("Cannot get an unique mailing list id after tried 20 times.") if not __is_mlid_exists(mlid=mlid, conn=conn): break else: _counter += 1 mlid = mlmmj.generate_mlid() return mlid @decorators.require_domain_access def num_maillists_under_domain(domain, disabled_only=False, first_char=None, conn=None): if not iredutils.is_domain(domain): return 0 num = 0 sql_vars = {'domain': domain} sql_where = '' if disabled_only: sql_where = ' AND active=0' if first_char: sql_where += ' AND address LIKE %s' % web.sqlquote(first_char.lower() + '%') if not conn: _wrap = SQLWrap() conn = _wrap.conn try: qr = conn.select('maillists', vars=sql_vars, what='COUNT(address) AS total', where='domain=$domain %s' % sql_where) num = qr[0].total or 0 except: log_traceback() return num def num_maillists_managed_by_user(mail, first_char=None, conn=None): mail = str(mail).lower() if not iredutils.is_email(mail): return 0 num = 0 sql_vars = {'mail': mail} sql_where = '' if first_char: sql_where += ' AND address LIKE %s' % web.sqlquote(first_char.lower() + '%') if not conn: _wrap = SQLWrap() conn = _wrap.conn mls = set() try: # It's possible user is moderator and owner of same list, so we need to # avoid duplication here, using SQL query `COUNT(*) AS total` is wrong. qr = conn.select('moderators', vars=sql_vars, what='address', where='moderator=$mail %s' % sql_where) for i in qr: addr = i["address"].strip().lower() mls.add(addr) qr = conn.select('maillist_owners', vars=sql_vars, what='address', where='owner=$mail %s' % sql_where) for i in qr: addr = i["address"].strip().lower() mls.add(addr) num = len(mls) except: log_traceback() return num def get_first_char_of_all_managed_mls(mail, conn=None): """Get first character of managed mailing lists. @mail - must be a valid email address or mailing list owner or moderator. @conn - SQL connection cursor """ if not conn: _wrap = SQLWrap() conn = _wrap.conn chars = [] for tbl, col in [("moderators", "moderator"), ("maillist_owners", "owner")]: try: qr = conn.select(tbl, vars={'mail': mail}, what="SUBSTRING(address FROM 1 FOR 1) AS first_char", where='{}=$mail'.format(col), group='first_char') if qr: chars = [str(i.first_char).upper() for i in qr if i not in chars] chars.sort() except Exception as e: log_traceback() return False, repr(e) return True, chars def get_basic_ml_profiles(domain, columns=None, first_char=None, page=0, disabled_only=False, email_only=False, conn=None): """Get all maillists under domain. Return data: (True, [{'mail': 'list@domain.com', 'name': '...', ...other profiles in `vmail.maillists` table... }]) """ domain = web.safestr(domain).lower() if not iredutils.is_domain(domain): raise web.seeother('/domains?msg=INVALID_DOMAIN_NAME') sql_vars = {'domain': domain} if columns: sql_what = ','.join(columns) else: sql_what = '*' if email_only: sql_what = 'address' additional_sql_where = '' if first_char: additional_sql_where = ' AND address LIKE %s' % web.sqlquote(first_char.lower() + '%') if disabled_only: additional_sql_where = ' AND active=0' # Get basic profiles try: if not conn: _wrap = SQLWrap() conn = _wrap.conn if page: qr = conn.select('maillists', vars=sql_vars, what=sql_what, where='domain=$domain %s' % additional_sql_where, order='address ASC', limit=settings.PAGE_SIZE_LIMIT, offset=(page - 1) * settings.PAGE_SIZE_LIMIT) else: qr = conn.select('maillists', vars=sql_vars, what=sql_what, where='domain=$domain %s' % additional_sql_where, order='address ASC') if email_only: emails = [i.address for i in qr] emails.sort() return True, emails else: _profiles = list(qr) return True, _profiles except Exception as e: return False, repr(e) def get_basic_profiles_of_managed_mls(first_char=None, page=1, conn=None): """Get profile of owned/moderated mailing lists. Return data: (True, [ {'mail': 'list@domain.com', 'name': '...', ...other profiles in `vmail.maillists` table... } ]) """ mail = session["username"] if page < 1: page = 1 sql_vars = {"mail": mail} if not conn: _wrap = SQLWrap() conn = _wrap.conn # Get owned / moderated mailing lists. mls = set() for tbl, col in [("maillist_owners", "owner"), ("moderators", "moderator")]: try: qr = conn.select(tbl, vars=sql_vars, what="address", where="{}=$mail".format(col)) for i in qr: addr = i["address"].strip().lower() mls.add(addr) except: pass if first_char: char = first_char.lower() mls = [i for i in mls if i.startswith(char)] if not mls: return True, [] sql_vars["mls"] = mls # Get basic profiles try: if page: qr = conn.select('maillists', vars=sql_vars, where="address IN $mls", order='address ASC', limit=settings.PAGE_SIZE_LIMIT, offset=(page - 1) * settings.PAGE_SIZE_LIMIT) _profiles = list(qr) return True, _profiles except Exception as e: return False, repr(e) @decorators.require_domain_access def add_ml_from_web_form(domain, form, conn=None): """ Add mailing list account from web from. :param domain: an valid domain name :param form: a dict of web form data :param conn: sql connection cursor """ domain = str(domain).lower() # Get domain name, username, cn. form_domain = form_utils.get_domain_name(form) listname = web.safestr(form.get('listname')).strip().lower() if not (domain == form_domain): return False, 'PERMISSION_DENIED' if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' mail = listname + '@' + domain if not iredutils.is_auth_email(mail): return False, 'INVALID_MAIL' if not conn: _wrap = SQLWrap() conn = _wrap.conn # Check account existing. if sql_lib_general.is_email_exists(mail=mail, conn=conn): return False, 'ALREADY_EXISTS' # Get domain profile. qr_profile = sql_lib_domain.profile(conn=conn, domain=domain) if qr_profile[0]: domain_profile = qr_profile[1] else: return qr_profile # Check account limit. num_exist = num_maillists_under_domain(conn=conn, domain=domain) if domain_profile.maillists == -1: return False, 'NOT_ALLOWED' elif domain_profile.maillists > 0: if domain_profile.maillists <= num_exist: return False, 'EXCEEDED_DOMAIN_ACCOUNT_LIMIT' try: mlmmj_params = form_utils.get_mlmmj_params_from_web_form(form) # Append relay_host if settings.AMAVISD_QUARANTINE_HOST: mlmmj_params['relay_host'] = settings.AMAVISD_QUARANTINE_HOST # create mlmmj account qr = mlmmj.create_account(mail=mail, form=mlmmj_params) if not qr[0]: return qr params = { 'active': 1, 'address': mail, 'domain': domain, 'name': form_utils.get_name(form=form, input_name='name'), 'maxmsgsize': mlmmj_params['max_message_size'], 'transport': mlmmj.generate_transport(mail), 'mlid': __get_new_mlid(conn=conn), 'created': iredutils.get_gmttime(), 'accesspolicy': mlmmj_params['access_policy'], } conn.insert('maillists', **params) # Add a forwarding record in sql table: `vmail.forwardings` # it will help make things easier like avoiding multiple SQL queries # to check whether an email address is existing. params = { 'address': mail, 'forwarding': mail, 'domain': domain, 'dest_domain': domain, 'is_maillist': 1, 'active': 1, } conn.insert('forwardings', **params) log_activity(msg="Create mailing list: %s." % mail, domain=domain, event='create') # Add `postmaster@` as owner and moderator. for tbl, col in [("maillist_owners", "owner"), ("moderators", "moderator")]: try: row = { "address": mail, col: "postmaster@" + domain, "domain": domain, "dest_domain": domain, } conn.insert(tbl, **row) except: pass # Add subscribers. For RESTful API. if 'subscribers' in form: _subscribers = form_utils.get_multi_values_from_api(form=form, input_name='subscribers', is_email=True) if _subscribers: _subscription = form.get('subscription', 'normal') # Store members in mlmmj. qr = mlmmj.add_subscribers(mail=mail, subscribers=_subscribers, subscription=_subscription, require_confirm=False) if not qr[0]: return qr return True, except Exception as e: if e.__class__.__name__ == 'IntegrityError': return False, 'ALREADY_EXISTS' else: return False, repr(e) def get_profile(mail, with_subscribers=False, conn=None): if not iredutils.is_email(mail): return False, 'INVALID_MAIL' try: if not conn: _wrap = SQLWrap() conn = _wrap.conn qr = conn.select('maillists', vars={'address': mail}, where='address=$address') if qr: profile = list(qr)[0] # get mlmmj profile _qr = mlmmj.get_account_profile(mail=mail, with_subscribers=with_subscribers) if _qr[0]: profile.update(_qr[1]) else: return _qr return True, profile else: return False, 'NO_SUCH_ACCOUNT' except Exception as e: return False, repr(e) def get_profile_by_mlid(mlid, conn=None): if not iredutils.is_mlid(mlid): return False, 'INVALID_' try: if not conn: _wrap = SQLWrap() conn = _wrap.conn qr = conn.select('maillists', vars={'mlid': mlid}, where='is_newsletter=1 AND mlid=$mlid AND active=1', limit=1) if qr: profile = list(qr)[0] return True, profile else: return False, 'NO_SUCH_ACCOUNT' except Exception as e: return False, repr(e) def get_alias_addresses(mail, conn=None): if not iredutils.is_email(mail): return False, 'INVALID_MAIL' if not conn: _wrap = SQLWrap() conn = _wrap.conn addresses = [] try: qr = conn.select( 'forwardings', vars={'mail': mail}, what='address', where='forwarding=$mail AND is_alias=1', ) for row in qr: addresses.append(str(row['address']).lower()) addresses.sort() return True, addresses except Exception as e: return False, repr(e) def update(mail, profile_type, form, conn=None): mail = web.safestr(mail).lower() domain = mail.split('@', 1)[-1] if not iredutils.is_email(mail): return False, 'INVALID_MAIL' if not conn: _wrap = SQLWrap() conn = _wrap.conn mlmmj_params = form_utils.get_mlmmj_params_from_web_form(form) params = {} if profile_type == 'general': params['name'] = form.get('name', '') params['accesspolicy'] = mlmmj_params['access_policy'] params['maxmsgsize'] = mlmmj_params['max_message_size'] # Do not allow mailing list owner / moderator to enable/disable list. if session.get('is_global_admin') or session.get('is_normal_admin'): params['active'] = 0 if 'accountStatus' in form: # Enabled. params['active'] = 1 params['modified'] = iredutils.get_gmttime() try: # Update profile conn.update('maillists', vars={'address': mail}, where='address=$address', **params) # Do not allow mailing list owner / moderator to enable/disable list. if session.get('is_global_admin') or session.get('is_normal_admin'): conn.update('forwardings', vars={'address': mail}, where='address=$address', active=params['active']) qr = mlmmj.update_account_profile(mail=mail, data=mlmmj_params) if not qr[0]: return qr # Log changes. msg = "Update maillist profile (%s)." % mail log_activity(msg=msg, username=mail, domain=domain, event='update') except Exception as e: return False, repr(e) elif profile_type == 'aliases': # Do not allow mailing list owner / moderator to enable/disable list. if not (session.get('is_global_admin') or session.get('is_normal_admin')): return False, "PERMISSION_DENIED" # Per-account alias addresses. alias_addresses = form_utils.get_multi_values_from_textarea( form=form, input_name='account_alias_addresses', is_email=True, to_lowercase=True, ) # Delete all existing per-account alias addresses first. conn.delete('forwardings', vars={'mail': mail}, where='forwarding=$mail AND is_alias=1') alias_addresses = [i for i in alias_addresses if i.split('@', 1)[-1] == domain] if not alias_addresses: return True, qr = sql_lib_general.filter_existing_emails(mails=alias_addresses, conn=conn) alias_addresses = qr['nonexist'] if not alias_addresses: return True, rows = [] for addr in alias_addresses: row = { 'address': addr, 'forwarding': mail, 'domain': addr.split('@', 1)[-1], 'dest_domain': domain, 'is_alias': 1, } rows.append(row) conn.multiple_insert('forwardings', rows) elif profile_type == 'members': action = form.get('action') if action == 'remove': # Remove members try: subscribers = [str(i).strip().lower() for i in form.get('subscriber', []) if iredutils.is_email(i)] if subscribers: qr = mlmmj.remove_subscribers(mail=mail, subscribers=subscribers) if not qr[0]: return qr except Exception as e: return False, repr(e) elif action == 'remove_all': # Remove all members from mlmmj. qr = mlmmj.remove_all_subscribers(mail=mail) if not qr[0]: return qr elif profile_type == 'owners': # Do not allow mailing list owner / moderator to update owner/moderators. if not (session.get('is_global_admin') or session.get('is_normal_admin')): return False, "PERMISSION_DENIED" kvs = {} for k in ['owners', 'moderators', 'subscription_moderators']: _addresses = form_utils.get_multi_values(form=form, input_name=k, default_value='', input_is_textarea=True, is_email=True, to_lowercase=True, to_string=True) if mail in _addresses: _addresses.remove(mail) kvs[k] = list(set(_addresses)) # Remove non-exist accounts under same domain _addresses_in_domain = set() for _values in list(kvs.values()): _addrs = [i for i in _values if i.endswith('@' + domain)] _addresses_in_domain.update(_addrs) qr = sql_lib_general.filter_existing_emails(mails=_addresses_in_domain, conn=conn) _exists = qr['exist'] for (k, v) in list(kvs.items()): _ext = [i for i in v if not i.endswith('@' + domain)] _int = [i for i in v if i.endswith('@' + domain) and i in _exists] kvs[k] = ', '.join(_ext + _int) kvs['moderate_subscription'] = mlmmj_params.get('moderate_subscription') qr = mlmmj.update_account_profile(mail=mail, data=kvs) if not qr[0]: return qr # Store owners and moderators in SQL db. # [(, , ), ...] for k, tbl, col in [("moderators", "moderators", "moderator"), ("owners", "maillist_owners", "owner")]: try: conn.delete(tbl, vars={'address': mail}, where='address=$address') if kvs[k]: _addresses = kvs[k].split(',') _addresses = [i.strip().lower() for i in _addresses if iredutils.is_email(i)] records = [] for _addr in _addresses: params = { 'address': mail, col: _addr, 'domain': domain, 'dest_domain': _addr.split('@', 1)[-1], } records.append(params) conn.multiple_insert(tbl, records) except Exception as e: return False, repr(e) # Log changes. for k in kvs: msg = "Update maillist {} ({}): {}".format(k, mail, kvs[k]) log_activity(msg=msg, username=mail, domain=domain, event='update') elif profile_type == 'newsletter': params['is_newsletter'] = 0 if 'is_newsletter' in form: params['is_newsletter'] = 1 params['description'] = form_utils.get_single_value(form=form, input_name='description') try: conn.update('maillists', vars={'address': mail}, where='address=$address', **params) except Exception as e: return False, repr(e) return True, def delete_maillists(accounts, keep_archive=True, conn=None): """Delete mailing lists under same domain.""" if not accounts: return True, # Get domain from first account domain = accounts[0].split('@', 1)[-1] if not iredutils.is_domain(domain): return True, sql_vars = { 'domain': domain, 'accounts': accounts, } try: if not conn: _wrap = SQLWrap() conn = _wrap.conn for tbl in ['maillists', 'maillist_owners', 'moderators']: conn.delete(tbl, vars=sql_vars, where='address IN $accounts') conn.delete('forwardings', vars=sql_vars, where='address IN $accounts OR forwarding IN $accounts') log_activity(event='delete', domain=accounts[0].split('@', 1)[-1], msg="Delete mailing lists: %s." % ', '.join(accounts)) except Exception as e: return False, repr(e) # Remove mailing list from domain.settings: default_groups qr = sql_lib_domain.remove_default_maillists_in_domain_setting( domain=domain, maillists=accounts, conn=conn, ) if not qr[0]: return qr # Remove mlmmj accounts qr = mlmmj.delete_accounts(mails=accounts, keep_archive=keep_archive) if not qr[0]: return qr return True, def get_subscribers(mail, email_only=False): mail = str(mail).lower() if not iredutils.is_email(mail): return False, 'INVALID_EMAIL' qr = mlmmj.get_subscribers(mail=mail, email_only=email_only) return qr def add_subscribers(mail, form, conn=None): mail = str(mail).lower() domain = mail.split('@', 1)[-1] if not iredutils.is_email(mail): return False, 'INVALID_EMAIL' if not conn: _wrap = SQLWrap() conn = _wrap.conn if not sql_lib_general.is_ml_exists(mail=mail, conn=conn): return False, 'INVALID_EMAIL' _subscription = form.get('subscription', 'normal') _subscribers = form_utils.get_multi_values(form=form, input_name='new_subscribers', default_value='', input_is_textarea=True, is_email=True, to_lowercase=True, to_string=False) _internal_subs = {i for i in _subscribers if i.endswith('@' + domain)} _subscribers = {i for i in _subscribers if not i.endswith('@' + domain)} _qr = sql_lib_general.filter_existing_emails(mails=_internal_subs, conn=None) _internal_subs = _qr['exist'] _subscribers.update(_internal_subs) qr = mlmmj.add_subscribers(mail=mail, subscribers=_subscribers, subscription=_subscription) return qr def migrate_alias_to_ml(mail, conn=None): """Migrate mail alias account to subscribable mlmmj mailing list.""" mail = mail.lower() (listname, domain) = mail.split('@', 1) if not conn: _wrap = SQLWrap() conn = _wrap.conn sql_vars = {'mail': mail} # Get profile of old alias account qr = conn.select('alias', vars=sql_vars, where='address=$mail', limit=1) if not qr: return False, 'INVALID_MAIL' profile_alias = list(qr)[0] # # Create mlmmj account # params = { 'mail': mail, 'subject_prefix': '[%s]' % listname, } # access policy access_policy = profile_alias.get('accesspolicy', '') if access_policy == 'membersonly': params['only_subscriber_can_post'] = 'yes' params['only_moderator_can_post'] = 'no' elif access_policy in ['moderatorsonly', 'allowedonly']: params['only_subscriber_can_post'] = 'no' params['only_moderator_can_post'] = 'yes' # Disable subscription/unsubscription by default params['close_list'] = 'yes' # Use postmaster@ as default owner params['owner'] = 'postmaster@' + domain # Append relay_host if settings.AMAVISD_QUARANTINE_HOST: params['relay_host'] = settings.AMAVISD_QUARANTINE_HOST qr = mlmmj.create_account(mail=mail, form=params) if not qr[0]: return qr # Migrate members. qr = conn.select('forwardings', vars=sql_vars, where='address=$mail', what='forwarding') members = set() for i in qr: members.add(str(i.forwarding).strip().lower()) if members: qr = mlmmj.add_subscribers(mail=mail, subscribers=members, subscription='normal', require_confirm=False) if not qr[0]: return qr del members # Migrate moderators. # Note: No need to update sql records of moderators. qr = conn.select('moderators', vars=sql_vars, where='address=$mail', what='moderator') moderators = set() for i in qr: moderators.add(str(i.moderator).lower()) if moderators: qr = mlmmj.update_account_profile(mail=mail, data={'moderators': ','.join(moderators)}) if not qr[0]: return qr del moderators # Create new mailing list account in SQL param = { 'address': mail, 'domain': domain, 'name': profile_alias.get('name', ''), 'active': profile_alias.get('active', 1), 'accesspolicy': access_policy, 'mlid': __get_new_mlid(conn=conn), 'transport': mlmmj.generate_transport(mail), } try: conn.insert('maillists', **param) # Delete all records with `address=`. # List members are stored in mlmmj data directory. conn.delete('forwardings', vars=sql_vars, where='address=$mail') conn.insert('forwardings', address=mail, forwarding=mail, domain=domain, dest_domain=domain, is_maillist=1, is_list=0, is_forwarding=0, is_alias=0) except Exception as e: return False, repr(e) # remove old alias account try: conn.delete('alias', vars={'mail': mail}, where='address=$mail') return True, except Exception as e: return False, repr(e) @decorators.require_domain_access def api_update_profile(mail, form, conn=None): mail = web.safestr(mail).lower() domain = mail.split('@', 1)[-1] if not iredutils.is_email(mail): return False, 'INVALID_MAIL' if not conn: _wrap = SQLWrap() conn = _wrap.conn # Parameters stored in SQL db. kvs = {} # Name kv = form_utils.get_form_dict(form=form, input_name='name') kvs.update(kv) # Account status _account_status = form_utils.get_account_status(form=form, input_name='accountStatus', default_value='active', to_integer=True) if _account_status in [1, 0]: kvs['active'] = _account_status # Max message size kv = form_utils.get_form_dict(form=form, input_name='max_message_size', key_name='maxmsgsize') kvs.update(kv) # Get access policy if 'accessPolicy' in form: _policy = form_utils.get_single_value(form=form, input_name='accessPolicy', to_string=True, to_lowercase=True) if _policy == 'membersonly': form['only_subscriber_can_post'] = 'yes' form['only_moderator_can_post'] = 'no' elif _policy in ['moderatorsonly', 'allowedonly']: form['only_subscriber_can_post'] = 'no' form['only_moderator_can_post'] = 'yes' else: if _policy in iredutils.ML_ACCESS_POLICIES: form['only_subscriber_can_post'] = 'no' form['only_moderator_can_post'] = 'no' kvs['accesspolicy'] = _policy # Although below two parameters maybe set by 'Get access policy' section, # we still need to handle the ones posted from client directly. if 'only_moderator_can_post' in form: if form.get('only_moderator_can_post', 'no') == 'yes': kvs['accesspolicy'] = 'moderatorsonly' elif 'only_subscriber_can_post' in form: if form.get('only_subscriber_can_post', 'no') == 'yes': kvs['accesspolicy'] = 'membersonly' # Newsletter if 'is_newsletter' in form: if form.get('is_newsletter') == 'yes': kvs['is_newsletter'] = 1 else: kvs['is_newsletter'] = 0 kv = form_utils.get_form_dict(form=form, input_name='newsletter_description', key_name='description') kvs.update(kv) # Store moderators/owners in SQL db. # [(, , ), ...] for k, tbl, col in [("moderators", "moderators", "moderator"), ("owners", "maillist_owners", "owner")]: if k in form: _addresses = form_utils.get_multi_values_from_api(form=form, input_name=k, is_email=True) # Remove duplicate addresses. _addresses = list(set(_addresses)) # Remove self if mail in _addresses: _addresses.remove(mail) # Delete existing moderators first. try: conn.delete(tbl, vars={'address': mail}, where='address=$address') except Exception as e: return False, repr(e) if _addresses: # Store moderators in SQL records = [] for _addr in _addresses: params = { 'address': mail, col: _addr, 'domain': domain, 'dest_domain': _addr.split('@', 1)[-1], } records.append(params) try: conn.multiple_insert(tbl, records) # Log changes. msg = "Reset mailing list ({}) {} to: {}".format(mail, k, ', '.join(_addresses)) log_activity(msg=msg, username=mail, domain=domain, event='update') except Exception as e: return False, repr(e) try: kvs['modified'] = iredutils.get_gmttime() # Update profile conn.update('maillists', vars={'address': mail}, where='address=$address', **kvs) conn.update('forwardings', vars={'address': mail}, where='address=$address', active=kvs['active']) # Log changes. msg = "Update maillist profile (%s)." % mail log_activity(msg=msg, username=mail, domain=domain, event='update') except Exception as e: return False, repr(e) mlmmj_params = form_utils.get_mlmmj_params_from_api(form=form) qr = mlmmj.update_account_profile(mail=mail, data=mlmmj_params) if not qr[0]: return qr # Add/remove subscribers if 'add_subscribers' in form: _subscribers = form_utils.get_multi_values_from_api(form=form, input_name='add_subscribers', is_email=True) if _subscribers: _subscription = form.get('subscription', 'normal') _require_confirm = (form.get('require_confirm') == "yes") qr = mlmmj.add_subscribers(mail=mail, subscribers=_subscribers, subscription=_subscription, require_confirm=_require_confirm) if not qr[0]: return qr if 'remove_subscribers' in form: _subscribers = form_utils.get_multi_values_from_api(form=form, input_name='remove_subscribers', is_email=True) if _subscribers: qr = mlmmj.remove_subscribers(mail=mail, subscribers=_subscribers) if not qr[0]: return qr return True,