Update to 5.4

This commit is contained in:
Copium-Snorter
2023-06-20 20:23:44 +01:00
parent 2991096ba7
commit 7b6f07ed6e
47 changed files with 138 additions and 89 deletions

View File

@@ -1,3 +1,14 @@
# 5.4
* RESTful API:
+ `GET /api/users/<domain>`: Export used quota info.
* Fixed issues:
- [API] Disabling domain causes losing inbound BCC email address.
- Can not save few per-admin privileges.
- Not respect server wide min/max password lengths while adding new user.
- tools/cleanup_amavisd_db.py: Can not clean up old SQL records if
column "quar_type" value is null.
# 5.3 # 5.3
+ Ship Python module web.py (github.com/webpy/webpy, public domain). + Ship Python module web.py (github.com/webpy/webpy, public domain).
* RESTful API: * RESTful API:

16
EULA Normal file
View File

@@ -0,0 +1,16 @@
* What's included within iRedAdmin-Pro license
- Free upgrade with valid license.
* Restrictions
- One license per server. Customer must purchase license for each server.
- Customer has to renew the license of iRedAdmin-Pro to continue using
iRedAdmin-Pro before license expired.
- After license expired, customer has to purchase a new license to continue
using iRedAdmin-Pro.
- NOT allowed to redistribute and/or resell original iRedAdmin-Pro source
code and the copy you modified based on iRedAdmin-Pro.

0
controllers/sql/urls.py Normal file → Executable file
View File

Binary file not shown.

View File

@@ -9,11 +9,11 @@ msgstr ""
"Last-Translator: Shafeek SUMSER <shafeeks@gmail.com>\n" "Last-Translator: Shafeek SUMSER <shafeeks@gmail.com>\n"
"Language: fr_FR\n" "Language: fr_FR\n"
"Language-Team: fr_FR <LL@li.org>\n" "Language-Team: fr_FR <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n" "Generated-By: Babel 2.11.0\n"
#, python-format #, python-format
msgid "%d admin(s) found." msgid "%d admin(s) found."
@@ -118,7 +118,7 @@ msgid "Account is disabled."
msgstr "Compte désactivé." msgstr "Compte désactivé."
msgid "Account is domain admin" msgid "Account is domain admin"
msgstr "Compte désactivé." msgstr "Compte administrateur du domaine."
msgid "Account is global admin" msgid "Account is global admin"
msgstr "Le compte est un administrateur global." msgstr "Le compte est un administrateur global."

View File

@@ -1,5 +1,5 @@
__author__ = "Zhang Huangbin" __author__ = "Zhang Huangbin"
__author_mail__ = "zhb@iredmail.org" __author_mail__ = "zhb@iredmail.org"
__version_ldap__ = "5.4" __version_ldap__ = "5.5"
__version_sql__ = "5.3" __version_sql__ = "5.4"
__url_license_terms__ = "http://www.iredmail.org/pricing.html#EULA" __url_license_terms__ = "http://www.iredmail.org/pricing.html#EULA"

View File

@@ -7,30 +7,36 @@ from libs import iredutils
# - Amavisd-new-2.6.x: [ A-Z, a-z, 0-9, +, - ] # - Amavisd-new-2.6.x: [ A-Z, a-z, 0-9, +, - ]
MAIL_ID_CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-_' MAIL_ID_CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-_'
WBLIST_FORM_INPUT_NAMES = {'wl_sender': 'whitelistSender', WBLIST_FORM_INPUT_NAMES = {
'bl_sender': 'blacklistSender', 'wl_sender': 'whitelistSender',
'wl_rcpt': 'whitelistRecipient', 'bl_sender': 'blacklistSender',
'bl_rcpt': 'blacklistRecipient'} 'wl_rcpt': 'whitelistRecipient',
'bl_rcpt': 'blacklistRecipient',
}
# Available quarantined types in iRedAdmin web interface, and the short code # Available quarantined types in iRedAdmin web interface, and the short code
# in `amavisd.msgs` sql table. # in `amavisd.msgs` sql table.
QUARANTINE_TYPES = {'spam': 'S', QUARANTINE_TYPES = {
'virus': 'V', 'spam': 'S',
'banned': 'B', 'virus': 'V',
'clean': 'C', 'banned': 'B',
'badheader': 'H', 'clean': 'C',
'badmime': 'M'} 'badheader': 'H',
'badmime': 'M',
}
# Value of `msgs.content` and comment. # Value of `msgs.content` and comment.
CONTENT_TYPES = {'B': 'Banned', CONTENT_TYPES = {
'C': 'Clean', 'B': 'Banned',
'H': 'Bad header', 'C': 'Clean',
'M': 'Bad mime', 'H': 'Bad header',
'O': 'Oversized', 'M': 'Bad mime',
'S': 'Spam', 'O': 'Oversized',
'T': 'MTA error', 'S': 'Spam',
'V': 'Virus', 'T': 'MTA error',
'U': 'Unchecked'} 'V': 'Virus',
'U': 'Unchecked',
}
def get_wblist_from_form(form, form_input_name): def get_wblist_from_form(form, form_input_name):

View File

@@ -98,7 +98,7 @@ def get_quarantined_mails(page=1,
elif account_type == 'user': elif account_type == 'user':
if session.get('is_normal_admin'): if session.get('is_normal_admin'):
# Make sure account is under managed domains # Make sure account is under managed domains
if not account.split('@', 1)[-1] in all_domains: if account.split('@', 1)[-1] not in all_domains:
# PERMISSION_DENIED # PERMISSION_DENIED
return True, (0, {}) return True, (0, {})
elif session.get('account_is_mail_user'): elif session.get('account_is_mail_user'):

View File

@@ -371,7 +371,7 @@ def get_account_status(form,
to_integer=False): to_integer=False):
status = get_single_value(form, input_name=input_name, to_string=True) status = get_single_value(form, input_name=input_name, to_string=True)
if not (status in ['active', 'disabled']): if status not in ['active', 'disabled']:
status = default_value status = default_value
# SQL backends store the account status as `active=[1|0]` # SQL backends store the account status as `active=[1|0]`

View File

@@ -31,7 +31,7 @@ def delete_throttle_setting(account, inout_type):
if not iredutils.is_valid_amavisd_address(account): if not iredutils.is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT' return False, 'INVALID_ACCOUNT'
if not (inout_type in ['inbound', 'outbound']): if inout_type not in ['inbound', 'outbound']:
return False, 'INVALID_INOUT_TYPE' return False, 'INVALID_INOUT_TYPE'
if account and inout_type: if account and inout_type:

0
libs/iredbase.py Normal file → Executable file
View File

View File

@@ -677,7 +677,7 @@ def generate_random_strings(length=10) -> str:
"23456789" "23456789"
s = "" s = ""
for x in range(length): for _ in range(length):
s += random.choice(chars) s += random.choice(chars)
return s return s

0
libs/l10n.py Normal file → Executable file
View File

View File

@@ -52,7 +52,11 @@ def list_logs(event='all', domain='all', admin='all', cur_page=1):
listed_only=True, listed_only=True,
conn=None) conn=None)
if qr[0]: if qr[0]:
sql_vars["managed_domains"] = qr[1] managed_domains = qr[1]
if not managed_domains:
return 0, []
sql_vars["managed_domains"] = managed_domains
sql_wheres += ["domain IN $managed_domains"] sql_wheres += ["domain IN $managed_domains"]
else: else:
return qr return qr

View File

@@ -1129,7 +1129,7 @@ def update(conn, mail, profile_type, form):
# If marked as normal domain admin, allow to create new domains # If marked as normal domain admin, allow to create new domains
# #
if 'allowed_to_create_domain' in form: if 'allowed_to_create_domain' in form:
_new_settings = {'create_new_domains': 'yes'} _new_settings['create_new_domains'] = 'yes'
for i in ['create_max_domains', for i in ['create_max_domains',
'create_max_quota', 'create_max_quota',
@@ -2257,14 +2257,17 @@ def get_basic_user_profiles(domain,
for row in rows: for row in rows:
email = row.username email = row.username
used_bytes = 0 _bytes = 0
used_messages = 0 _messages = 0
if email in used_quota_info: if email in used_quota_info:
used_bytes = used_quota_info[email]["bytes"] _bytes = used_quota_info[email]["bytes"]
used_messages = used_quota_info[email]["messages"] _messages = used_quota_info[email]["messages"]
row["used_quota"] = {"bytes": used_bytes, "messages": used_messages} row["used_quota"] = {
"bytes": _bytes,
"messages": _messages,
}
return True, rows return True, rows
except Exception as e: except Exception as e:

View File

@@ -264,7 +264,7 @@ def search(search_string,
# Add new, remove duplicate records. # Add new, remove duplicate records.
for i in _records: for i in _records:
if not (i in result['user']): if i not in result['user']:
result['user'] += [i] result['user'] += [i]
if qr_user_forwarding: if qr_user_forwarding:
@@ -272,7 +272,7 @@ def search(search_string,
# Add new, remove duplicate records. # Add new, remove duplicate records.
for i in _records: for i in _records:
if not (i in result['user']): if i not in result['user']:
result['user'] += [i] result['user'] += [i]
# Get email addresses of returned user accounts # Get email addresses of returned user accounts

0
rc_scripts/iredadmin.debian Normal file → Executable file
View File

0
rc_scripts/iredadmin.freebsd Normal file → Executable file
View File

0
rc_scripts/iredadmin.openbsd Normal file → Executable file
View File

0
rc_scripts/iredadmin.rhel Normal file → Executable file
View File

View File

@@ -1,3 +1,5 @@
* Please read file 'EULA' for End User License Agreement.
* If you already have iRedAdmin open source edition or old iRedAdmin-Pro * If you already have iRedAdmin open source edition or old iRedAdmin-Pro
release installed, please follow below tutorial to upgrade it to the latest release installed, please follow below tutorial to upgrade it to the latest
iRedAdmin-Pro, it's the easiest way with minimal steps: iRedAdmin-Pro, it's the easiest way with minimal steps:

8
static/.htaccess Executable file
View File

@@ -0,0 +1,8 @@
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
</IfModule>

0
static/default/css/spectre-icons.min.css vendored Normal file → Executable file
View File

0
static/default/css/spectre.min.css vendored Normal file → Executable file
View File

File diff suppressed because one or more lines are too long

2
static/js/jquery-3.6.4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

0
static/js/jquery.quickfilter.js Normal file → Executable file
View File

0
static/js/stupidtable.min.js vendored Normal file → Executable file
View File

View File

@@ -5,7 +5,6 @@
{% set token = token |e %} {% set token = token |e %}
{% endif %} {% endif %}
<!--suppress ALL -->
<input type="hidden" name="csrf_token" value="{{ token }}"/> <input type="hidden" name="csrf_token" value="{{ token }}"/>
{%- endmacro %} {%- endmacro %}

View File

@@ -6,7 +6,7 @@
{# ------------ Load JS files ------------- #} {# ------------ Load JS files ------------- #}
{% macro load_jquery() -%} {% macro load_jquery() -%}
<script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery-1.12.4.min.js"></script> <script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery-3.6.4.min.js"></script>
<script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery.tooltip.js"></script> <script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery.tooltip.js"></script>
<script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery.idtabs.js"></script> <script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery.idtabs.js"></script>
<script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery.fancybox.js"></script> <script type="text/javascript" src="{{ctx.homepath}}/static/js/jquery.fancybox.js"></script>

View File

@@ -330,36 +330,39 @@
{% endif %} {% endif %}
{# profile_type: throttling #} {# profile_type: throttling #}
{% if session.get('is_global_admin') or 'throttle' not in disabled_domain_profiles %} {% if (session.get('is_global_admin') or 'throttle' not in disabled_domain_profiles) and session.get('iredapd_enabled') %}
<div id="profile_throttle"> <div id="profile_throttle">
<form name="throttle" method="post" action="{{ctx.homepath}}/profile/domain/throttle/{{cur_domain}}"> <form name="throttle"
method="post"
action="{{ctx.homepath}}/profile/domain/throttle/{{cur_domain}}"
>
{{ input_csrf_token() }} {{ input_csrf_token() }}
{% if session.get('iredapd_enabled') %} {% if session.get('iredapd_enabled') %}
{# Throttling with iRedAPD #} {# Throttling with iRedAPD #}
<div class="columns clear"> <div class="columns clear">
{{ input_csrf_token() }} {{ input_csrf_token() }}
{{ display_throttle_setting(account='@' + cur_domain, {{ display_throttle_setting(account='@' + cur_domain,
setting=outbound_throttle_setting, setting=outbound_throttle_setting,
inout_type='outbound') }} inout_type='outbound') }}
{{ display_throttle_setting(account='@' + cur_domain,
setting=inbound_throttle_setting,
inout_type='inbound',
with_left_border=true) }}
<div class="col1-3 lastcol"> {{ display_throttle_setting(account='@' + cur_domain,
<div class="mark_blue bt-space10"> setting=inbound_throttle_setting,
<ul class="standard clean-padding bt-space10"> inout_type='inbound',
<li class="bt-space5">{{ _('This throttle setting will be applied to all individual accounts under domain %s.') |format(cur_domain) }}</li> with_left_border=true) }}
<li class="bt-space5">{{ _('You can set per-user throttling in account profile page.') }}</li>
<li class="bt-space5">{{ _('Per-user throttle setting has higher priority.') }}</li> <div class="col1-3 lastcol">
</ul> <div class="mark_blue bt-space10">
</div> <ul class="standard clean-padding bt-space10">
</div>{#-- .col1-3 --#} <li class="bt-space5">{{ _('This throttle setting will be applied to all individual accounts under domain %s.') |format(cur_domain) }}</li>
</div>{# .columns #} <li class="bt-space5">{{ _('You can set per-user throttling in account profile page.') }}</li>
{% endif %} <li class="bt-space5">{{ _('Per-user throttle setting has higher priority.') }}</li>
</ul>
</div>
</div>{#-- .col1-3 --#}
</div>{# .columns #}
{% endif %}
{{ input_submit() }} {{ input_submit() }}
</form> </form>

View File

@@ -31,13 +31,13 @@
import os import os
import sys import sys
import time import time
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from libs import iredutils from libs import iredutils
from tools import ira_tool_lib from tools import ira_tool_lib

View File

@@ -27,13 +27,13 @@
import os import os
import sys import sys
import time import time
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from tools.ira_tool_lib import debug, logger, sql_dbn, get_db_conn, sql_count_id from tools.ira_tool_lib import debug, logger, sql_dbn, get_db_conn, sql_count_id

View File

@@ -40,13 +40,13 @@ import time
import logging import logging
import shutil import shutil
import pwd import pwd
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
from libs import iredutils from libs import iredutils
from tools import ira_tool_lib from tools import ira_tool_lib
import settings import settings

View File

@@ -6,13 +6,13 @@
import os import os
import sys import sys
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
from tools import ira_tool_lib from tools import ira_tool_lib
web.config.debug = ira_tool_lib.debug web.config.debug = ira_tool_lib.debug

View File

@@ -26,9 +26,6 @@
import os import os
import sys import sys
import web
web.config.debug = False
# Directory used to store disclaimer files. # Directory used to store disclaimer files.
# Default directory is /etc/postfix/disclaimer/. # Default directory is /etc/postfix/disclaimer/.
@@ -43,11 +40,14 @@ os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from libs import iredutils from libs import iredutils
from tools import ira_tool_lib from tools import ira_tool_lib
logger = ira_tool_lib.logger logger = ira_tool_lib.logger
web.config.debug = False
if settings.backend == 'ldap': if settings.backend == 'ldap':
import ldap import ldap
elif settings.backend == 'mysql': elif settings.backend == 'mysql':

View File

@@ -9,7 +9,6 @@
import os import os
import sys import sys
import time import time
import web
output_dir = sys.argv[1] output_dir = sys.argv[1]
if not os.path.isdir(output_dir): if not os.path.isdir(output_dir):
@@ -20,6 +19,7 @@ os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
from tools.ira_tool_lib import debug, get_db_conn from tools.ira_tool_lib import debug, get_db_conn
web.config.debug = debug web.config.debug = debug

View File

@@ -14,13 +14,13 @@ Usage:
import os import os
import sys import sys
import time import time
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from tools import ira_tool_lib from tools import ira_tool_lib
from libs.iredutils import epoch_seconds_to_gmt from libs.iredutils import epoch_seconds_to_gmt

View File

@@ -5,21 +5,21 @@
import os import os
import sys import sys
import logging import logging
import web
debug = False debug = False
# Set True to print SQL queries.
web.config.debug = debug
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from libs import iredutils from libs import iredutils
# Set True to print SQL queries.
web.config.debug = debug
backend = settings.backend backend = settings.backend
if backend in ['ldap', 'mysql']: if backend in ['ldap', 'mysql']:
sql_dbn = 'mysql' sql_dbn = 'mysql'

View File

@@ -8,13 +8,13 @@
import os import os
import sys import sys
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from libs.iredutils import is_valid_amavisd_address from libs.iredutils import is_valid_amavisd_address
from libs.amavisd import wblist from libs.amavisd import wblist

View File

@@ -93,7 +93,6 @@ import time
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.header import Header from email.header import Header
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
@@ -103,6 +102,7 @@ sys.path.insert(0, rootdir)
now = int(time.time()) now = int(time.time())
import web
import settings import settings
from libs import iredutils from libs import iredutils
from libs.ireddate import utc_to_timezone from libs.ireddate import utc_to_timezone

View File

@@ -15,13 +15,13 @@ def usage():
import os import os
import sys import sys
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from tools.ira_tool_lib import debug, get_db_conn from tools.ira_tool_lib import debug, get_db_conn
from libs.iredutils import is_email from libs.iredutils import is_email

View File

@@ -14,13 +14,13 @@ def usage():
import os import os
import sys import sys
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from tools.ira_tool_lib import debug, get_db_conn from tools.ira_tool_lib import debug, get_db_conn
from libs.iredutils import is_email from libs.iredutils import is_email

View File

@@ -32,13 +32,13 @@ def usage():
import os import os
import sys import sys
import web
os.environ['LC_ALL'] = 'C' os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from tools.ira_tool_lib import debug, logger, get_db_conn from tools.ira_tool_lib import debug, logger, get_db_conn
from libs.iredutils import is_email from libs.iredutils import is_email

View File

@@ -5,7 +5,6 @@
import os import os
import sys import sys
import web
def usage(): def usage():
@@ -33,6 +32,7 @@ os.environ['LC_ALL'] = 'C'
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../' rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
sys.path.insert(0, rootdir) sys.path.insert(0, rootdir)
import web
import settings import settings
from tools.ira_tool_lib import debug, logger, get_db_conn from tools.ira_tool_lib import debug, logger, get_db_conn
from libs.iredutils import is_email from libs.iredutils import is_email

0
web/utils.py Normal file → Executable file
View File