mirror of
https://github.com/marcus-alicia/iRedAdmin-Pro-SQL.git
synced 2026-05-26 07:08:10 +00:00
Add files via upload
This commit is contained in:
44
tools/README.md
Normal file
44
tools/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Cron Jobs
|
||||
|
||||
* dump_disclaimer.py
|
||||
|
||||
Dump per-domain disclaimer which stored in LDAP or SQL database.
|
||||
It's safe to execute it manually.
|
||||
|
||||
* cleanup_amavisd_db.py
|
||||
|
||||
Cleanup old records from Amavisd database. It's safe to execute it manually.
|
||||
|
||||
* delete_mailboxes.py
|
||||
|
||||
Delete mailboxes which are scheduled to be removed. The schedule date
|
||||
was set while you removed the mail account with iRedAdmin(-Pro).
|
||||
|
||||
# Utils
|
||||
|
||||
* upgrade_iredadmin.sh
|
||||
|
||||
Upgrade an old iRedAdmin-Pro or iRedAdmin open source edition to current
|
||||
release.
|
||||
|
||||
* update_mailbox_quota.py
|
||||
|
||||
Update mailbox quota for one user (specified on command line) or bulk users
|
||||
(read from a plain text file).
|
||||
|
||||
* notify_quarantined_recipients.py
|
||||
|
||||
Notify local recipients (via email) that they have emails quarantined on
|
||||
server and not delivered to their mailbox.
|
||||
|
||||
* convert_ini_to_py.sh
|
||||
|
||||
Convert old iRedAdmin-Pro config file (.ini format) to the new one.
|
||||
|
||||
* migrate_cluebringer_wblist_to_amavisd.py
|
||||
|
||||
Migrate Cluebringer white/blacklists to Amavisd database, and, optionally,
|
||||
delete them in Cluebringer database.
|
||||
|
||||
Note: Don't forget to enable iRedAPD plugin `amavisd_wblist` in
|
||||
`/opt/iredapd/settings.py`.
|
||||
0
tools/__init__.py
Normal file
0
tools/__init__.py
Normal file
264
tools/cleanup_amavisd_db.py
Normal file
264
tools/cleanup_amavisd_db.py
Normal file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Remove old records in Amavisd database.
|
||||
|
||||
# USAGE:
|
||||
#
|
||||
# 1: Make sure you have correct database settings in iRedAdmin config file
|
||||
# 'settings.py' for Amavisd.
|
||||
#
|
||||
# 2: Make sure you have proper values for below two parameters:
|
||||
#
|
||||
# AMAVISD_REMOVE_MAILLOG_IN_DAYS = 7
|
||||
# AMAVISD_REMOVE_QUARANTINED_IN_DAYS = 7
|
||||
#
|
||||
# Default values is defined in libs/default_settings.py, you can override
|
||||
# them in settings.py. WARNING: DO NOT MODIFY libs/default_settings.py.
|
||||
#
|
||||
# 3: Test this script in command line directly, make sure no errors in output
|
||||
# message.
|
||||
#
|
||||
# # python cleanup_amavisd_db.py
|
||||
#
|
||||
# 4: Setup a daily cron job to execute this script. For example: execute
|
||||
# it daily at 1:30AM.
|
||||
#
|
||||
# 30 1 * * * python /path/to/cleanup_amavisd_db.py >/dev/null
|
||||
#
|
||||
# That's all.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from libs import iredutils
|
||||
from tools import ira_tool_lib
|
||||
|
||||
web.config.debug = ira_tool_lib.debug
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
if not (settings.amavisd_enable_logging or settings.amavisd_enable_quarantine):
|
||||
sys.exit("Amavisd is not enabled. SKIP.")
|
||||
|
||||
backend = settings.backend
|
||||
logger.info('Backend: %s' % backend)
|
||||
logger.info('SQL server: %s:%d' % (settings.amavisd_db_host, int(settings.amavisd_db_port)))
|
||||
|
||||
db_settings = iredutils.get_settings_from_db(params=['amavisd_remove_quarantined_in_days', 'amavisd_remove_maillog_in_days'])
|
||||
keep_quar_days = db_settings['amavisd_remove_quarantined_in_days']
|
||||
keep_inout_days = db_settings['amavisd_remove_maillog_in_days']
|
||||
query_size_limit = settings.AMAVISD_CLEANUP_QUERY_SIZE_LIMIT
|
||||
|
||||
# SQL records in `quarantine` table reference to `msgs`.
|
||||
if keep_quar_days > keep_inout_days:
|
||||
keep_inout_days = keep_quar_days
|
||||
|
||||
conn_amavisd = ira_tool_lib.get_db_conn('amavisd')
|
||||
|
||||
if settings.backend in ['mysql', 'ldap']:
|
||||
# Querying (SELECT) without locking. Require MySQL 5.0+ and InnoDB.
|
||||
#
|
||||
# Since we're dealing with sql records created days ago, no new records
|
||||
# will be inserted with that date, it's safe to use dirty read.
|
||||
logger.info('Enable dirty read for querying without locking SQL tables.')
|
||||
try:
|
||||
conn_amavisd.query('SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED')
|
||||
except Exception as e:
|
||||
logger.error('Cannot enable dirty read: %s' % repr(e))
|
||||
|
||||
|
||||
# Removing records from single table.
|
||||
def remove_from_one_table(sql_table, index_column, removed_values):
|
||||
total = len(removed_values)
|
||||
|
||||
# Delete how many records each time
|
||||
offset = query_size_limit
|
||||
|
||||
if total:
|
||||
loop_times = total / offset
|
||||
if total % offset:
|
||||
loop_times += 1
|
||||
|
||||
for i in range(int(loop_times)):
|
||||
removing_values = removed_values[offset * i: offset * (i + 1)]
|
||||
logger.info(
|
||||
'\t[-] Deleting records: %d - %d (%s)' % (i * offset, i * offset + len(removing_values), time.ctime()))
|
||||
conn_amavisd.delete(sql_table,
|
||||
vars={'ids': removing_values},
|
||||
where='%s IN $ids' % index_column)
|
||||
|
||||
|
||||
# Delete old quarantined mails from table 'msgs'. It will also
|
||||
# delete records in table 'quarantine'.
|
||||
logger.info('Delete quarantined mails which older than %d days' % keep_quar_days)
|
||||
_now = int(time.time())
|
||||
_expire_seconds = _now - (keep_quar_days * 86400)
|
||||
sql_where = """time_num < %d AND quar_type='Q'""" % _expire_seconds
|
||||
|
||||
counter_msgs = 0
|
||||
while True:
|
||||
qr = conn_amavisd.select('msgs',
|
||||
what='mail_id',
|
||||
where=sql_where,
|
||||
limit=query_size_limit)
|
||||
|
||||
if qr:
|
||||
ids = [r.mail_id for r in qr]
|
||||
_total = len(ids)
|
||||
|
||||
logger.info('\t[-] Deleting records: %d - %d (%s)' % (counter_msgs + 1, counter_msgs + _total, time.ctime()))
|
||||
|
||||
conn_amavisd.delete('msgs', vars={'ids': ids}, where='mail_id IN $ids')
|
||||
conn_amavisd.delete('msgrcpt', vars={'ids': ids}, where='mail_id IN $ids')
|
||||
|
||||
counter_msgs += len(ids)
|
||||
else:
|
||||
break
|
||||
|
||||
logger.info('Delete incoming/outgoing emails which older than %d days' % keep_inout_days)
|
||||
|
||||
_now = int(time.time())
|
||||
_expire_seconds = _now - (keep_inout_days * 86400)
|
||||
sql_where = """time_num < %d AND (quar_type <> 'Q' OR quar_type IS NULL)""" % _expire_seconds
|
||||
|
||||
# We experienced an issue with PostgreSQL, it always return an non-existing
|
||||
# SQL record, and it causes endless loop. As a hack, we store all removed
|
||||
# `mail_id` and compare new `mail_id` with this list.
|
||||
_removed_ids = set()
|
||||
|
||||
counter_msgrcpt = 0
|
||||
while True:
|
||||
qr = conn_amavisd.select('msgs',
|
||||
what='mail_id',
|
||||
where=sql_where,
|
||||
limit=query_size_limit)
|
||||
|
||||
if qr:
|
||||
ids = [iredutils.bytes2str(r.mail_id) for r in qr]
|
||||
_total = len(ids)
|
||||
|
||||
_removing_ids = list(set(ids) - set(_removed_ids))
|
||||
if not _removing_ids:
|
||||
break
|
||||
|
||||
logger.info(
|
||||
'\t[-] Deleting records: %d - %d (%s)' % (counter_msgrcpt + 1, counter_msgrcpt + _total, time.ctime()))
|
||||
|
||||
conn_amavisd.delete('msgs', vars={'ids': _removing_ids}, where='mail_id IN $ids')
|
||||
conn_amavisd.delete('msgrcpt', vars={'ids': _removing_ids}, where='mail_id IN $ids')
|
||||
|
||||
counter_msgrcpt += _total
|
||||
_removed_ids.update(ids)
|
||||
else:
|
||||
break
|
||||
|
||||
# delete unreferenced records from tables msgrcpt, quarantine and maddr
|
||||
logger.info('Delete unreferenced records from table `msgrcpt`.')
|
||||
conn_amavisd.query('''
|
||||
DELETE FROM msgrcpt
|
||||
WHERE NOT EXISTS (SELECT 1 FROM msgs WHERE mail_id=msgrcpt.mail_id)
|
||||
''')
|
||||
|
||||
#
|
||||
# Delete unreferenced records from table `quarantine`.
|
||||
#
|
||||
logger.info('Delete unreferenced records from table `quarantine`.')
|
||||
msgs_mail_ids = set()
|
||||
maddr_ids_in_use = set()
|
||||
quar_mail_ids = set()
|
||||
|
||||
qr = conn_amavisd.select('msgs', what='mail_id, sid')
|
||||
for i in qr:
|
||||
msgs_mail_ids.add(i.mail_id)
|
||||
maddr_ids_in_use.add(i.sid)
|
||||
|
||||
qr = conn_amavisd.select('quarantine', what='mail_id')
|
||||
for i in qr:
|
||||
quar_mail_ids.add(i.mail_id)
|
||||
|
||||
invalid_quar_mail_ids = [i for i in quar_mail_ids if i not in msgs_mail_ids]
|
||||
remove_from_one_table(sql_table='quarantine',
|
||||
index_column='mail_id',
|
||||
removed_values=invalid_quar_mail_ids)
|
||||
|
||||
#
|
||||
# Delete unreferenced records from table `maddr`.
|
||||
#
|
||||
logger.info('Delete unreferenced records from table `maddr`.')
|
||||
|
||||
# Get all maddr.id
|
||||
maddr_ids = set()
|
||||
qr = conn_amavisd.select('maddr', what='id')
|
||||
for i in qr:
|
||||
maddr_ids.add(i.id)
|
||||
|
||||
qr = conn_amavisd.select('msgrcpt', what='rid')
|
||||
for i in qr:
|
||||
maddr_ids_in_use.add(i.rid)
|
||||
|
||||
invalid_maddr_ids = [i for i in maddr_ids if i not in maddr_ids_in_use]
|
||||
remove_from_one_table(sql_table='maddr',
|
||||
index_column='id',
|
||||
removed_values=invalid_maddr_ids)
|
||||
|
||||
#
|
||||
# Delete unreferenced records from table `mailaddr`.
|
||||
#
|
||||
logger.info('Delete unreferenced records from table `mailaddr`.')
|
||||
|
||||
# Get all `mailaddr.id`
|
||||
mailaddr_ids = set()
|
||||
qr = conn_amavisd.select('mailaddr', what='id')
|
||||
for i in qr:
|
||||
mailaddr_ids.add(i.id)
|
||||
|
||||
# Get all `wblist.sid` and `outbound_wblist.rid` (both refer to `mailaddr.id`)
|
||||
wblist_ids = set()
|
||||
|
||||
qr = conn_amavisd.select('wblist', what='sid')
|
||||
for i in qr:
|
||||
wblist_ids.add(i.sid)
|
||||
|
||||
try:
|
||||
qr = conn_amavisd.select('outbound_wblist', what='rid')
|
||||
for i in qr:
|
||||
wblist_ids.add(i.rid)
|
||||
except:
|
||||
# No outbound_wblist table
|
||||
pass
|
||||
|
||||
invalid_mailaddr_ids = [i for i in mailaddr_ids if i not in wblist_ids]
|
||||
remove_from_one_table(sql_table='mailaddr',
|
||||
index_column='id',
|
||||
removed_values=invalid_mailaddr_ids)
|
||||
|
||||
logger.info('')
|
||||
logger.info('Remained records:')
|
||||
logger.info('')
|
||||
logger.info(' `msgs`: %-7.d' % len(msgs_mail_ids))
|
||||
logger.info('`quarantine`: %-7.d' % (len(quar_mail_ids) - len(invalid_quar_mail_ids)))
|
||||
logger.info(' `maddr`: %-7.d' % (len(maddr_ids) - len(invalid_maddr_ids)))
|
||||
logger.info(' `mailaddr`: %-7.d' % (len(mailaddr_ids) - len(invalid_mailaddr_ids)))
|
||||
|
||||
if counter_msgs \
|
||||
or counter_msgrcpt \
|
||||
or invalid_quar_mail_ids \
|
||||
or invalid_maddr_ids \
|
||||
or invalid_mailaddr_ids:
|
||||
msg = 'Removed records: '
|
||||
msg += '%d in msgs, ' % counter_msgs
|
||||
msg += '%d in msgrcpt, ' % counter_msgrcpt
|
||||
msg += '%d in quarantine, ' % len(invalid_quar_mail_ids)
|
||||
msg += '%d in maddr, ' % len(invalid_maddr_ids)
|
||||
msg += '%d in mailaddr.' % len(invalid_mailaddr_ids)
|
||||
|
||||
ira_tool_lib.log_to_iredadmin(msg, admin='cleanup_amavisd_db', event='cleanup_db')
|
||||
logger.info('Log cleanup status.')
|
||||
91
tools/cleanup_db.py
Normal file
91
tools/cleanup_db.py
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Remove old records in iRedAdmin SQL database.
|
||||
|
||||
# USAGE:
|
||||
#
|
||||
# 1: Make sure you have proper values for below two parameters:
|
||||
#
|
||||
# IREDADMIN_LOG_KEPT_DAYS = 30
|
||||
#
|
||||
# Default values is defined in libs/default_settings.py, you can override
|
||||
# them in settings.py. WARNING: DO NOT MODIFY libs/default_settings.py.
|
||||
#
|
||||
# 2: Test this script in command line directly, make sure no errors in output
|
||||
# message.
|
||||
#
|
||||
# # python cleanup_db.py
|
||||
#
|
||||
# 3: Setup a daily cron job to execute this script. For example: execute
|
||||
# it daily at 1:30AM.
|
||||
#
|
||||
# 30 1 * * * python /path/to/cleanup_db.py >/dev/null
|
||||
#
|
||||
# That's all.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from tools.ira_tool_lib import debug, logger, sql_dbn, get_db_conn, sql_count_id
|
||||
|
||||
web.config.debug = debug
|
||||
|
||||
backend = settings.backend
|
||||
logger.info('Backend: %s' % backend)
|
||||
logger.info('SQL server: %s:%d' % (settings.iredadmin_db_host, int(settings.iredadmin_db_port)))
|
||||
|
||||
query_size_limit = 100
|
||||
|
||||
conn_iredadmin = get_db_conn('iredadmin')
|
||||
|
||||
#
|
||||
# iredadmin.log
|
||||
#
|
||||
_days = settings.IREDADMIN_LOG_KEPT_DAYS
|
||||
logger.info('Delete old admin activity log (> %d days)' % _days)
|
||||
|
||||
if sql_dbn == 'mysql':
|
||||
sql_where = """timestamp < DATE_SUB(NOW(), INTERVAL %d DAY)""" % _days
|
||||
elif sql_dbn == 'postgres':
|
||||
sql_where = """timestamp < CURRENT_TIMESTAMP - INTERVAL '%d DAYS'""" % _days
|
||||
else:
|
||||
logger.error('Invalid SQL backend: %s' % sql_dbn)
|
||||
sys.exit()
|
||||
|
||||
total_before = sql_count_id(conn_iredadmin, 'log')
|
||||
conn_iredadmin.delete('log', where=sql_where)
|
||||
total_after = sql_count_id(conn_iredadmin, 'log')
|
||||
logger.info('\t- %d removed, %d left.' % (total_before - total_after, total_after))
|
||||
|
||||
#
|
||||
# iredadmin.domain_ownership
|
||||
#
|
||||
_days = settings.DOMAIN_OWNERSHIP_EXPIRE_DAYS
|
||||
logger.info('Delete old domain ownership verification records (> %d days)' % _days)
|
||||
|
||||
total_before = sql_count_id(conn_iredadmin, 'domain_ownership')
|
||||
conn_iredadmin.delete('domain_ownership', where="expire > %d" % (_days * 24 * 60 * 60))
|
||||
total_after = sql_count_id(conn_iredadmin, 'domain_ownership')
|
||||
logger.info('\t- %d removed, %d left.' % (total_before - total_after, total_after))
|
||||
|
||||
#
|
||||
# iredadmin.newsletter_subunsub_confirms
|
||||
#
|
||||
now = int(time.time())
|
||||
_hours = settings.NEWSLETTER_SUBSCRIPTION_REQUEST_KEEP_HOURS
|
||||
logger.info('Delete expired newsletter subscription confirm tokens (> %d hours)' % _hours)
|
||||
|
||||
total_before = sql_count_id(conn_iredadmin, 'newsletter_subunsub_confirms')
|
||||
_expired = now - (_hours * 60 * 60)
|
||||
conn_iredadmin.delete('newsletter_subunsub_confirms', where="expired <= %d" % _expired)
|
||||
total_after = sql_count_id(conn_iredadmin, 'newsletter_subunsub_confirms')
|
||||
logger.info('\t- %d removed, %d left.' % (total_before - total_after, total_after))
|
||||
229
tools/delete_mailboxes.py
Normal file
229
tools/delete_mailboxes.py
Normal file
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Delete mailboxes which are scheduled to be removed.
|
||||
#
|
||||
# Notes: iRedAdmin will store maildir path of removed mail users in SQL table
|
||||
# `iredadmin.deleted_mailboxes` (LDAP backends) or
|
||||
# `vmail.deleted_mailboxes` (SQL backends).
|
||||
#
|
||||
# Usage: Either run this script manually, or run it with a daily cron job.
|
||||
#
|
||||
# # python3 delete_mailboxes.py
|
||||
#
|
||||
# Available arguments:
|
||||
#
|
||||
# * --delete-without-timestamp:
|
||||
#
|
||||
# [RISKY] If no timestamp string in maildir path, continue to delete it.
|
||||
#
|
||||
# With default iRedMail settings, maildir path will contain a timestamp
|
||||
# like this: <domain.com>/u/s/e/username-2016.08.17.09.53.03/
|
||||
# (2016.08.17.09.53.03 is the timestamp), this way all created maildir
|
||||
# paths are unique, even if you removed the user and recreate it with
|
||||
# same mail address.
|
||||
#
|
||||
# Without timestamp in maildir path (e.g. <domain.com>/u/s/e/username/),
|
||||
# if you removed a user and recreate it someday, this user will see old
|
||||
# emails in old mailbox (because maildir path is same as old user's). So
|
||||
# it becomes RISKY to remove the mailbox if no timestamp in maildir path.
|
||||
#
|
||||
# * --delete-null-date:
|
||||
#
|
||||
# Delete mailbox if SQL column `deleted_mailboxes.delete_date` is null.
|
||||
#
|
||||
# * --debug: print additional log
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import shutil
|
||||
import pwd
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
from libs import iredutils
|
||||
from tools import ira_tool_lib
|
||||
import settings
|
||||
|
||||
web.config.debug = ira_tool_lib.debug
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
if '--debug' in sys.argv:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Delete if `deleted_mailboxes.delete_date` is null.
|
||||
delete_null_date = False
|
||||
if '--delete-null-date' in sys.argv:
|
||||
delete_null_date = True
|
||||
|
||||
# Make sure there's a timestamp (yyyy.mm.dd.hh.mm.ss) in maildir path,
|
||||
# otherwise it's too risky to remove this mailbox -- because the maildir
|
||||
# could be reused by another user after old account was removed.
|
||||
#
|
||||
# - Safe to remove: <domain.com>/u/s/e/username-<timestamp>/
|
||||
# - Dangerous to remove: <domain.com>/u/s/e/username/
|
||||
delete_without_timestamp = False
|
||||
if '--delete-without-timestamp' in sys.argv:
|
||||
delete_without_timestamp = True
|
||||
|
||||
|
||||
def delete_record(conn_deleted_mailboxes, rid):
|
||||
try:
|
||||
conn_deleted_mailboxes.delete('deleted_mailboxes',
|
||||
vars={'id': rid},
|
||||
where='id=$id')
|
||||
|
||||
return True,
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
|
||||
def delete_mailbox(conn_deleted_mailboxes,
|
||||
record,
|
||||
all_maildirs=None):
|
||||
rid = record.id
|
||||
username = str(record.username).lower()
|
||||
timestamp = str(record.timestamp)
|
||||
delete_date = record.delete_date
|
||||
|
||||
maildir = record.maildir
|
||||
maildir = maildir.replace('//', '/') # Remove duplicate '/'
|
||||
|
||||
if delete_without_timestamp:
|
||||
# Make sure no other mailbox is stored under the maildir.
|
||||
if all_maildirs:
|
||||
if not maildir.endswith('/'):
|
||||
maildir += '/'
|
||||
|
||||
for mdir in all_maildirs:
|
||||
if mdir.startswith(maildir) or (mdir == maildir):
|
||||
logger.error("<<< ABORT, CRITICAL >>> Trying to remove mailbox ({}) owned by user ({}), but there is another mailbox ({}) stored under this directory. Aborted.".format(maildir, username, mdir))
|
||||
return False
|
||||
else:
|
||||
_dir = maildir.rstrip('/')
|
||||
|
||||
if len(_dir) <= 21:
|
||||
# Why 21 chars:
|
||||
# - 20 chars: "-<timestamp>". e.g. "-2014.03.26.15.07.25"
|
||||
# - username contains at least 1 char
|
||||
logger.error("<<< SKIP >>> Seems no timestamp in maildir path (%s), too risky to remove this mailbox." % maildir)
|
||||
return False
|
||||
|
||||
try:
|
||||
# Extract timestamp string, make sure it's a valid time format.
|
||||
ts = _dir[-19:]
|
||||
time.strptime(ts, '%Y.%m.%d.%H.%M.%S')
|
||||
except Exception as e:
|
||||
logger.debug("<<< WARNING >>> Invalid or missing timestamp in maildir path (%s), skip." % maildir)
|
||||
logger.debug("<<< WARNING >>> Error message: %s." % repr(e))
|
||||
return False
|
||||
|
||||
# check maildir path
|
||||
if os.path.isdir(maildir):
|
||||
# Make sure directory is owned by vmail:vmail
|
||||
_dir_stat = os.stat(maildir)
|
||||
_dir_uid = _dir_stat.st_uid
|
||||
|
||||
# Get uid/gid of vmail user
|
||||
owner = pwd.getpwuid(_dir_uid).pw_name
|
||||
if owner != 'vmail':
|
||||
logger.error('<<< ERROR >> Directory is not owned by `vmail` user: uid -> {}, user -> {}.'.format(_dir_uid, owner))
|
||||
return False
|
||||
|
||||
try:
|
||||
msg = '[{}] {}.'.format(username, maildir)
|
||||
msg += ' Account was deleted at {}.'.format(timestamp)
|
||||
if delete_date:
|
||||
msg += ' Mailbox was scheduled to be removed on {}.'.format(delete_date)
|
||||
else:
|
||||
msg += ' Mailbox was scheduled to be removed as soon as possible.'
|
||||
|
||||
logger.info(msg)
|
||||
|
||||
logger.info("Removing mailbox: {}".format(maildir))
|
||||
# Delete mailbox
|
||||
shutil.rmtree(maildir)
|
||||
|
||||
# Log this deletion.
|
||||
ira_tool_lib.log_to_iredadmin(msg,
|
||||
admin='cron_delete_mailboxes',
|
||||
username=username,
|
||||
event='delete_mailboxes')
|
||||
except Exception as e:
|
||||
logger.error('<<< ERROR >> while deleting mailbox ({} -> {}): {}'.format(username, maildir, repr(e)))
|
||||
|
||||
# Delete record.
|
||||
delete_record(conn_deleted_mailboxes=conn_deleted_mailboxes, rid=rid)
|
||||
|
||||
|
||||
# Establish SQL connection.
|
||||
try:
|
||||
if settings.backend == 'ldap':
|
||||
conn_deleted_mailboxes = ira_tool_lib.get_db_conn('iredadmin')
|
||||
|
||||
from libs.ldaplib.core import LDAPWrap
|
||||
_wrap = LDAPWrap()
|
||||
conn_vmail = _wrap.conn
|
||||
else:
|
||||
conn_deleted_mailboxes = ira_tool_lib.get_db_conn('vmail')
|
||||
conn_vmail = conn_deleted_mailboxes
|
||||
except Exception as e:
|
||||
sys.exit('<<< ERROR >>> Cannot connect to SQL database, aborted. Error: %s' % repr(e))
|
||||
|
||||
# Get paths of all maildirs.
|
||||
sql_where = 'delete_date <= %s' % web.sqlquote(web.sqlliteral('NOW()'))
|
||||
if delete_null_date:
|
||||
sql_where = '(delete_date <= %s) OR (delete_date IS NULL)' % web.sqlquote(web.sqlliteral('NOW()'))
|
||||
|
||||
qr_mailboxes = conn_deleted_mailboxes.select('deleted_mailboxes', where=sql_where)
|
||||
if not qr_mailboxes:
|
||||
logger.debug('No mailbox is scheduled to be removed.')
|
||||
|
||||
if not delete_null_date:
|
||||
logger.debug("To remove mailboxes without schedule date, please run this script with argument '--delete-null-date'.")
|
||||
|
||||
if not delete_without_timestamp:
|
||||
logger.debug("To remove mailboxes without timesamp in maildir path, please run this script with argument '--delete-without-timestamp'. [WARNING] It's RISKY.")
|
||||
|
||||
sys.exit()
|
||||
|
||||
# Get all maildir paths used by active mail users.
|
||||
#
|
||||
# To delete mailbox without timestamp in maildir path, we must make sure:
|
||||
# - maildir is not used by some active user
|
||||
# - no other mailbox is stored under this maildir path
|
||||
#
|
||||
# Q: Why query all maildir paths instead of querying SQL/LDAP directly?
|
||||
# A:
|
||||
# 1. LDAP attribute `homeDirectory` doesn't support `sub` (substring) index.
|
||||
# 2. if maildir path contains duplicate '/', the validation will fail (not
|
||||
# equal).
|
||||
all_maildirs = []
|
||||
if delete_without_timestamp:
|
||||
if settings.backend == 'ldap':
|
||||
_qr = conn_vmail.search_s(settings.ldap_basedn,
|
||||
2, # ldap.SCOPE_SUBTREE
|
||||
"(objectClass=mailUser)",
|
||||
['homeDirectory'])
|
||||
for (_dn, _ldif) in _qr:
|
||||
_ldif = iredutils.bytes2str(_ldif)
|
||||
if 'homeDirectory' in _ldif:
|
||||
_dir = _ldif['homeDirectory'][0].lower().replace('//', '/')
|
||||
all_maildirs.append(_dir)
|
||||
elif settings.backend in ['mysql', 'pgsql']:
|
||||
# WARNING: always append '/' in returned maildir path.
|
||||
_qr = conn_vmail.select('mailbox',
|
||||
what="LOWER(CONCAT(storagebasedirectory, '/', storagenode, '/', maildir, '/')) AS maildir")
|
||||
|
||||
all_maildirs = [str(i.maildir).replace('//', '/') for i in _qr]
|
||||
|
||||
for r in list(qr_mailboxes):
|
||||
delete_mailbox(conn_deleted_mailboxes=conn_deleted_mailboxes,
|
||||
record=r,
|
||||
all_maildirs=all_maildirs)
|
||||
24
tools/delete_sessions.py
Normal file
24
tools/delete_sessions.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Delete all records in SQL table "iredadmin.sessions" to force
|
||||
# all admins to re-login.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
from tools import ira_tool_lib
|
||||
|
||||
web.config.debug = ira_tool_lib.debug
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
conn = ira_tool_lib.get_db_conn('iredadmin')
|
||||
|
||||
logger.info('Delete all existing sessions to force all admins to re-login.')
|
||||
conn.query('DELETE FROM sessions')
|
||||
190
tools/dump_disclaimer.py
Normal file
190
tools/dump_disclaimer.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Updated: 2012.07.01
|
||||
# Purpose: Dump disclaimer text from OpenLDAP directory server or SQL servers.
|
||||
# Requirements: iRedMail-0.5.0 or later releases
|
||||
#
|
||||
# Shipped within iRedAdmin-Pro: http://www.iredmail.org/admin_panel.html
|
||||
|
||||
# USAGE:
|
||||
#
|
||||
# - Make sure you have correct backend related settings in iRedAdmin config
|
||||
# file, settings.ini.
|
||||
#
|
||||
# - Test this script in command line directly, make sure no errors in output
|
||||
# message.
|
||||
#
|
||||
# # python /path/to/dump_disclaimer.py /etc/postfix/disclaimer/
|
||||
#
|
||||
# - Setup a cron job to execute this script daily. For example: execute
|
||||
# this script at 2:01AM every day.
|
||||
#
|
||||
# 1 2 * * * python /path/to/dump_disclaimer.py /etc/postfix/disclaimer/
|
||||
#
|
||||
# That's all.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
web.config.debug = False
|
||||
|
||||
# Directory used to store disclaimer files.
|
||||
# Default directory is /etc/postfix/disclaimer/.
|
||||
# Default disclaimer file name is [domain_name].txt
|
||||
if len(sys.argv) != 2:
|
||||
sys.exit('Error: Please specify a directory used to store disclaimer, default is /etc/postfix/disclaimer/')
|
||||
else:
|
||||
DISCLAIMER_DIR = sys.argv[1]
|
||||
DISCLAIMER_FILE_EXT = '.txt'
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from libs import iredutils
|
||||
from tools import ira_tool_lib
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
if settings.backend == 'ldap':
|
||||
import ldap
|
||||
elif settings.backend == 'mysql':
|
||||
sql_dbn = 'mysql'
|
||||
elif settings.backend == 'pgsql':
|
||||
sql_dbn = 'postgres'
|
||||
|
||||
|
||||
def write_disclaimer(text, filename, file_type='txt'):
|
||||
# Write plain text
|
||||
try:
|
||||
f = open(filename, 'w')
|
||||
|
||||
if file_type == 'html':
|
||||
html = """<div id="disclaimer_separator"><p>----------</p><br /></div>"""
|
||||
html += """<div id="disclaimer_text"><p>""" + text + """</p></div>"""
|
||||
|
||||
f.write('\n' + html + '\n')
|
||||
else:
|
||||
f.write('\n---------\n' + text + '\n')
|
||||
logger.info(" + %s" % filename)
|
||||
f.close()
|
||||
except Exception as e:
|
||||
logger.info('<<< ERROR >>> %s' % str(e))
|
||||
|
||||
|
||||
def handle_disclaimer(domain, disclaimer_text):
|
||||
"""Dump or remove disclaimer text."""
|
||||
txt = os.path.join(DISCLAIMER_DIR, domain + '.txt')
|
||||
html = os.path.join(DISCLAIMER_DIR, domain + '.html')
|
||||
|
||||
if disclaimer_text:
|
||||
write_disclaimer(text=disclaimer_text,
|
||||
filename=txt,
|
||||
file_type='txt')
|
||||
|
||||
write_disclaimer(text=disclaimer_text,
|
||||
filename=html,
|
||||
file_type='html')
|
||||
else:
|
||||
# Remove old disclaimer file if no disclaimer setting
|
||||
try:
|
||||
for f in [txt, html]:
|
||||
if os.path.isfile(f):
|
||||
os.remove(f)
|
||||
logger.info(" - Remove %s." % f)
|
||||
except OSError:
|
||||
pass
|
||||
except Exception as e:
|
||||
# Other errors.
|
||||
logger.info("<<< ERROR >>> {}: {}.".format(domain, str(e)))
|
||||
|
||||
|
||||
def dump_from_ldap():
|
||||
"""Dump disclaimer text from LDAP server."""
|
||||
logger.info('Connecting to LDAP server')
|
||||
conn = ldap.initialize(uri=settings.ldap_uri,
|
||||
trace_level=0,
|
||||
bytes_strictness='silent')
|
||||
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
|
||||
|
||||
logger.info('Binding with dn: %s' % settings.ldap_basedn)
|
||||
conn.bind_s(settings.ldap_bind_dn, settings.ldap_bind_password)
|
||||
|
||||
# Search and get disclaimer.
|
||||
logger.info('Searching all domains')
|
||||
qr = conn.search_s(
|
||||
settings.ldap_basedn,
|
||||
ldap.SCOPE_ONELEVEL,
|
||||
'(objectClass=mailDomain)',
|
||||
['domainName', 'domainAliasName', 'disclaimer'],
|
||||
)
|
||||
|
||||
logger.info('Dumping ...')
|
||||
|
||||
for (_dn, _ldif) in qr:
|
||||
_ldif = iredutils.bytes2str(_ldif)
|
||||
|
||||
# Get domain names.
|
||||
_domains = _ldif['domainName']
|
||||
_alias_domains = _ldif.get('domainAliasName', [])
|
||||
disclaimer_text = _ldif.get('disclaimer', [''])[0]
|
||||
|
||||
domains = _domains + _alias_domains
|
||||
|
||||
for domain in domains:
|
||||
handle_disclaimer(domain, disclaimer_text)
|
||||
|
||||
conn.unbind()
|
||||
logger.info('Connection closed.')
|
||||
|
||||
|
||||
def dump_from_sql():
|
||||
"""Dump disclaimer text from MySQL or PostgreSQL server."""
|
||||
logger.info("Connecting to SQL server '%s:%d' as user '%s' ..." % (settings.vmail_db_host,
|
||||
int(settings.vmail_db_port),
|
||||
settings.vmail_db_user))
|
||||
|
||||
conn = web.database(dbn=sql_dbn,
|
||||
host=settings.vmail_db_host,
|
||||
port=int(settings.vmail_db_port),
|
||||
db=settings.vmail_db_name,
|
||||
user=settings.vmail_db_user,
|
||||
pw=settings.vmail_db_password)
|
||||
|
||||
logger.info('Get all alias domains')
|
||||
qr = conn.select('alias_domain', what='alias_domain, target_domain')
|
||||
alias_domains = {}
|
||||
for i in qr:
|
||||
_alias_domain = str(i.alias_domain).lower()
|
||||
_target_domain = str(i.target_domain).lower()
|
||||
|
||||
if _target_domain in alias_domains:
|
||||
alias_domains[_target_domain].append(_alias_domain)
|
||||
else:
|
||||
alias_domains[_target_domain] = [_alias_domain]
|
||||
|
||||
# Search and get disclaimer.
|
||||
logger.info('Get all primary domains')
|
||||
qr = conn.select('domain', what='domain, disclaimer')
|
||||
|
||||
# Dump disclaimer for every domain.
|
||||
logger.info('Dumping...')
|
||||
for r in qr:
|
||||
domain = str(r.domain).lower()
|
||||
disclaimer_text = r.disclaimer
|
||||
|
||||
domains = [domain] + alias_domains.get(domain, [])
|
||||
|
||||
logger.info(domain)
|
||||
for domain in domains:
|
||||
handle_disclaimer(domain, disclaimer_text)
|
||||
|
||||
logger.info('Completed.')
|
||||
|
||||
|
||||
if settings.backend == 'ldap':
|
||||
dump_from_ldap()
|
||||
elif settings.backend in ['mysql', 'pgsql']:
|
||||
dump_from_sql()
|
||||
88
tools/dump_quarantined_mails.py
Normal file
88
tools/dump_quarantined_mails.py
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Dump quarantined emails to given directory (specified on command line).
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# python dump_quarantined_mail.py /path/to/dir
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import web
|
||||
|
||||
output_dir = sys.argv[1]
|
||||
if not os.path.isdir(output_dir):
|
||||
sys.exit("Output directory doesn't exist: %s" % output_dir)
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
from tools.ira_tool_lib import debug, get_db_conn
|
||||
|
||||
web.config.debug = debug
|
||||
|
||||
now = int(time.time())
|
||||
conn_amavisd = get_db_conn('amavisd')
|
||||
conn_iredadmin = get_db_conn('iredadmin')
|
||||
|
||||
# Get last time
|
||||
last_time = 0
|
||||
try:
|
||||
qr = conn_iredadmin.select('tracking', what='v', where="k='dump_quarantined_mail'", limit=1)
|
||||
if qr:
|
||||
last_time = int(qr[0].v)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Get value of all `quarantine.mail_id`.
|
||||
try:
|
||||
qr = conn_amavisd.select(['msgs', 'quarantine'],
|
||||
what='msgs.mail_id AS mail_id',
|
||||
where='msgs.mail_id=quarantine.mail_id AND msgs.time_num >= %d' % last_time,
|
||||
group='msgs.mail_id')
|
||||
except Exception as e:
|
||||
print('<<< ERROR >>> {}'.format(repr(e)))
|
||||
sys.exit()
|
||||
|
||||
total = len(qr)
|
||||
print("* Found {} quarantined emails in SQL db.".format(total))
|
||||
|
||||
counter = 1
|
||||
for r in qr:
|
||||
mail_id = str(r.mail_id)
|
||||
try:
|
||||
records = conn_amavisd.select('quarantine',
|
||||
what='mail_text',
|
||||
where='mail_id = %s' % web.sqlquote(mail_id),
|
||||
order='chunk_ind ASC')
|
||||
|
||||
if not records:
|
||||
continue
|
||||
|
||||
# Combine mail_text as RAW mail message.
|
||||
message = ''
|
||||
for i in list(records):
|
||||
for j in i.mail_text:
|
||||
message += j
|
||||
|
||||
# Write message to file
|
||||
try:
|
||||
eml_path = os.path.join(output_dir, 'spam-' + mail_id)
|
||||
print("[{}/{}] Dumping email to file: {}".format(counter, total, eml_path))
|
||||
|
||||
f = open(eml_path, 'w')
|
||||
f.write(message)
|
||||
f.close()
|
||||
except Exception as e:
|
||||
print('<<< ERROR >>> cannot write file {}'.format(repr(e)))
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(repr(e)))
|
||||
|
||||
counter += 1
|
||||
|
||||
# Log last time.
|
||||
conn_iredadmin.delete('tracking', where="k='dump_quarantined_mail'")
|
||||
conn_iredadmin.insert('tracking', k='dump_quarantined_mail', v=now)
|
||||
96
tools/export_last_login.py
Normal file
96
tools/export_last_login.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Query user last login info from (My)SQL database and display it in a more
|
||||
readable format (plain text or html).
|
||||
|
||||
Note: You need to follow this tutorial to enable last_login plugin in Dovecot:
|
||||
https://docs.iredmail.org/track.user.last.login.html
|
||||
|
||||
Usage:
|
||||
|
||||
python3 export_last_login.py # in plain text format
|
||||
python3 export_last_login.py html > export.html # in html format
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from tools import ira_tool_lib
|
||||
from libs.iredutils import epoch_seconds_to_gmt
|
||||
|
||||
web.config.debug = ira_tool_lib.debug
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
if settings.backend == 'ldap':
|
||||
conn = ira_tool_lib.get_db_conn('iredadmin')
|
||||
else:
|
||||
conn = ira_tool_lib.get_db_conn('vmail')
|
||||
|
||||
# Get output format
|
||||
try:
|
||||
export_format = sys.argv[1]
|
||||
except:
|
||||
export_format = 'text'
|
||||
|
||||
try:
|
||||
qr = conn.select('last_login',
|
||||
order='last_login DESC')
|
||||
except Exception as e:
|
||||
sys.exit("Query failed: {}".format(e))
|
||||
|
||||
if export_format == 'html':
|
||||
_now = time.strftime('%Y-%d-%m %H:%M:%S')
|
||||
|
||||
html = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.th_size, .th_date, .td_size, .td_date {{ white-space: nowrap; }}
|
||||
.tr_date {{ background-color: #DDDDDD; }}
|
||||
.text_align_left {{ text-align: left; }}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h3>User Last Login Time ({0})</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Email</th>
|
||||
<th>Time (GMT)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
""".format(_now)
|
||||
|
||||
counter = 1
|
||||
for row in qr:
|
||||
username = row.username
|
||||
seconds = row.last_login
|
||||
last_login = epoch_seconds_to_gmt(seconds)
|
||||
|
||||
if export_format == 'html':
|
||||
html += """
|
||||
<tr>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
</tr>
|
||||
""".format(counter, username, last_login)
|
||||
else:
|
||||
print("{:6} | {:30} | {}".format(counter, username, last_login))
|
||||
|
||||
counter += 1
|
||||
|
||||
if export_format == 'html':
|
||||
html += """</tbody></table></body></html>"""
|
||||
print(html)
|
||||
216
tools/import_users.py
Normal file
216
tools/import_users.py
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
# Purpose: Read mail accounts from given plain text file (in specified format),
|
||||
# then create them with iRedAdmin-Pro RESTful API interface.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# - Make sure your iRedAdmin-Pro has RESTful API interface enabled by
|
||||
# following our tutorial:
|
||||
# https://docs.iredmail.org/iredadmin-pro.restful.api.html#enable-restful-api
|
||||
#
|
||||
# - Generate file /opt/users.list which contains the mail accounts you want
|
||||
# to import, one account per line, with account info stored in few fields:
|
||||
#
|
||||
# 1: [REQUIRED] user's full email address.
|
||||
# 2: [REQUIRED] plain text or password hash which starts with the password
|
||||
# scheme name. For example, "{SSHA}xxx", "{SSHA512}xxx".
|
||||
# 3: [optional] mailbox quota in MB. Must be an integer number.
|
||||
# 4: [optional] full display name.
|
||||
# 5: [optional] list of mailing list addresses. If not empty, user will be
|
||||
# assigned to given mailing lists as a member.
|
||||
#
|
||||
# Notes:
|
||||
#
|
||||
# - Multiple addresses must be separated by ":".
|
||||
# - If mailing list doesn't exist, it will not be created automatically.
|
||||
# 6: [optional] employeeid: employee id.
|
||||
#
|
||||
# NOTE: the separator "," for ending EMPTY optional fields is not required.
|
||||
#
|
||||
# Samples:
|
||||
#
|
||||
# user@domain.com, plain_password
|
||||
# user@domain.com, plain_password, 1024, Zhang Huangbin, list1@domain.com:list2@domain.com
|
||||
# user@domain.com, plain_password, , , list1@domain.com:list2@domain.com
|
||||
# user@domain.com, plain_password, 1024, Zhang Huangbin
|
||||
#
|
||||
# - Update 3 parameters in this file:
|
||||
#
|
||||
# api_endpoint = ''
|
||||
# verify_cert = True
|
||||
# admin = 'postmaster@a.io'
|
||||
# pw = 'password'
|
||||
#
|
||||
# - "api_endpoint" is the endpoint of iRedAdmin-Pro RESTful API.
|
||||
# - With "verify_cert = True", a valid ssl cert is required on API
|
||||
# server (https://). If you don't have a valid ssl cert yet, please set
|
||||
# it to False.
|
||||
# - "admin" is the email address of domain admin which has privilege to
|
||||
# manage the email domain which you're going to import users to.
|
||||
# - "pw" is plain password of domain admin.
|
||||
#
|
||||
# - Run commands below to create users listed in the "/opt/users.list" file:
|
||||
#
|
||||
# python import_users.py /opt/users.list
|
||||
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
# Endpoint of iRedAdmin-Pro RESTful API
|
||||
api_endpoint = 'http://127.0.0.1:8080/api'
|
||||
|
||||
# Verify SSL cert of API server.
|
||||
# If you don't have a valid SSL cert yet, please set it to False.
|
||||
verify_cert = True
|
||||
|
||||
# Domain admin email address and password
|
||||
admin = 'postmaster@a.io'
|
||||
pw = 'www'
|
||||
|
||||
# Define the order of fields in each line. Fields must be separated by comma.
|
||||
#
|
||||
#
|
||||
# WARNING: For empty optional fields, a comma is still required as placeholder.
|
||||
#
|
||||
# Samples:
|
||||
#
|
||||
# user@domain.com, plain_password, , ,
|
||||
# user@domain.com, plain_password, 1024, Zhang Huangbin, list1@domain.com:list2@domain.com,
|
||||
# user@domain.com, plain_password, , , list1@domain.com:list2@domain.com,
|
||||
# user@domain.com, plain_password, 1024, Zhang Huangbin,,
|
||||
#
|
||||
field_map = ['mail', 'password', 'quota', 'name', 'groups', 'employeeid']
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
from libs import iredutils
|
||||
|
||||
|
||||
def __get(url, data=None):
|
||||
_url = api_endpoint + url
|
||||
r = requests.get(_url, data=data, cookies=cookies, verify=verify_cert)
|
||||
return r.json()
|
||||
|
||||
|
||||
def __post(url, data=None):
|
||||
_url = api_endpoint + url
|
||||
r = requests.post(_url, data=data, cookies=cookies, verify=verify_cert)
|
||||
return r.json()
|
||||
|
||||
|
||||
def __put(url, data=None):
|
||||
_url = api_endpoint + url
|
||||
r = requests.put(_url, data=data, cookies=cookies, verify=verify_cert)
|
||||
return r.json()
|
||||
|
||||
|
||||
def __delete(url, data=None):
|
||||
_url = api_endpoint + url
|
||||
r = requests.delete(_url, data=data, cookies=cookies, verify=verify_cert)
|
||||
return r.json()
|
||||
|
||||
|
||||
def usage():
|
||||
pass
|
||||
|
||||
|
||||
if len(sys.argv) != 2 or len(sys.argv) > 2:
|
||||
print("Usage: $ python bulk_import.py /path/to/file")
|
||||
usage()
|
||||
sys.exit()
|
||||
else:
|
||||
file = sys.argv[1]
|
||||
if not os.path.exists(file):
|
||||
print("<<< ERROR >>> file does not exist: {}".format(file))
|
||||
sys.exit()
|
||||
|
||||
#
|
||||
# Login
|
||||
#
|
||||
r = requests.post(api_endpoint + '/login',
|
||||
data={'username': admin, 'password': pw},
|
||||
verify=verify_cert)
|
||||
|
||||
# Get returned JSON data
|
||||
res = r.json()
|
||||
if not res['_success']:
|
||||
sys.exit('Login failed')
|
||||
|
||||
cookies = r.cookies
|
||||
|
||||
# Read user list.
|
||||
f = open(file, 'rb')
|
||||
|
||||
for line in f.readlines():
|
||||
line = iredutils.bytes2str(line.strip())
|
||||
fields = line.split(',')
|
||||
|
||||
try:
|
||||
d = {}
|
||||
for (k, v) in zip(field_map, fields):
|
||||
d[k] = v
|
||||
except:
|
||||
sys.exit("<<< ERROR >>> line has invalid format:\n{}".format(line))
|
||||
|
||||
# Get user mail address
|
||||
mail = d.pop('mail')
|
||||
mail.lower()
|
||||
if not iredutils.is_email(mail):
|
||||
sys.exit("<<< ERROR >>> line has invalid user email address: {}\nLine: {}".format(mail, line))
|
||||
|
||||
password = d.pop('password')
|
||||
name = d.pop("name", mail.split("@", 1)[0])
|
||||
quota = d.pop("quota", "0")
|
||||
|
||||
# Get mail address(es) of assigned mailing list(s)
|
||||
groups = d.pop('groups', "")
|
||||
groups.lower()
|
||||
groups = [addr.lower().strip() for addr in groups.split(':') if iredutils.is_email(addr)]
|
||||
|
||||
# Create user
|
||||
res = __post('/user/' + mail,
|
||||
data={'name': name,
|
||||
'password': password.strip(),
|
||||
'quota': quota})
|
||||
|
||||
if res['_success']:
|
||||
print("[OK] Created user: {}".format(mail))
|
||||
else:
|
||||
if res['_msg'] == 'ALREADY_EXISTS':
|
||||
print("[SKIP] Account already exists: {}.".format(mail))
|
||||
continue
|
||||
else:
|
||||
sys.exit('<<< ERROR >>> failed to create user: {}'.format(res))
|
||||
|
||||
if password.startswith('{'):
|
||||
res = __put('/user/' + mail,
|
||||
data={'password_hash': password})
|
||||
|
||||
if res['_success']:
|
||||
print(" |- [OK] Updated user password (hash): {}".format(mail))
|
||||
else:
|
||||
sys.exit('<<< ERROR >>> failed to updated user password (hash): {}, error: {}'.format(mail, res))
|
||||
|
||||
if groups:
|
||||
for group in groups:
|
||||
res = __put('/ml/' + group,
|
||||
data={'add_subscribers': mail,
|
||||
'require_confirm': 'no'})
|
||||
|
||||
if res['_success']:
|
||||
print(" |- [OK] Subscribed user to mailing list: {} -> {}".format(mail, group))
|
||||
else:
|
||||
print('<<< WARNING >>> failed to subscribe user to mailing list: {} -> {}, error: {}'.format(mail, group, res))
|
||||
|
||||
employeeid = d.pop("employeeid", "")
|
||||
if employeeid:
|
||||
res = __put('/user/' + mail,
|
||||
data={'employeeid': employeeid})
|
||||
|
||||
if res['_success']:
|
||||
print(" |- [OK] Updated employeeid: {}".format(mail))
|
||||
else:
|
||||
sys.exit('<<< ERROR >>> failed to updated employeeid: {}, error: {}'.format(mail, res))
|
||||
99
tools/ira_tool_lib.py
Normal file
99
tools/ira_tool_lib.py
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Library used by other scripts under tools/ directory.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import web
|
||||
|
||||
debug = False
|
||||
|
||||
# Set True to print SQL queries.
|
||||
web.config.debug = debug
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from libs import iredutils
|
||||
|
||||
backend = settings.backend
|
||||
if backend in ['ldap', 'mysql']:
|
||||
sql_dbn = 'mysql'
|
||||
elif backend in ['pgsql']:
|
||||
sql_dbn = 'postgres'
|
||||
else:
|
||||
sys.exit('Error: Unsupported backend (%s).' % backend)
|
||||
|
||||
# logging
|
||||
logger = logging.getLogger('iredadmin')
|
||||
_ch = logging.StreamHandler(sys.stdout)
|
||||
_formatter = logging.Formatter('* %(message)s')
|
||||
_ch.setFormatter(_formatter)
|
||||
logger.addHandler(_ch)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
def get_db_conn(db_name):
|
||||
if backend == 'ldap' and db_name in ['ldap', 'vmail']:
|
||||
logger.error("""Please use code below to get LDAP connection cursor:\n
|
||||
|
||||
from libs.ldaplib.core import LDAPWrap\n
|
||||
_wrap = LDAPWrap()\n
|
||||
conn = _wrap.conn\n""")
|
||||
|
||||
return None
|
||||
|
||||
try:
|
||||
conn = web.database(
|
||||
dbn=sql_dbn,
|
||||
host=settings.__dict__[db_name + '_db_host'],
|
||||
port=int(settings.__dict__[db_name + '_db_port']),
|
||||
db=settings.__dict__[db_name + '_db_name'],
|
||||
user=settings.__dict__[db_name + '_db_user'],
|
||||
pw=settings.__dict__[db_name + '_db_password'],
|
||||
)
|
||||
|
||||
conn.supports_multiple_insert = True
|
||||
return conn
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return None
|
||||
|
||||
|
||||
# Log in `iredadmin.log`
|
||||
def log_to_iredadmin(msg, event, admin='', username='', loglevel='info'):
|
||||
conn = get_db_conn('iredadmin')
|
||||
|
||||
try:
|
||||
conn.insert('log',
|
||||
admin=admin,
|
||||
username=username,
|
||||
event=event,
|
||||
loglevel=loglevel,
|
||||
msg=str(msg),
|
||||
ip='127.0.0.1',
|
||||
timestamp=iredutils.get_gmttime())
|
||||
except:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def sql_count_id(conn, table, column='id', where=None):
|
||||
if where:
|
||||
qr = conn.select(table,
|
||||
what='count(%s) as total' % column,
|
||||
where=where)
|
||||
else:
|
||||
qr = conn.select(table,
|
||||
what='count(%s) as total' % column)
|
||||
if qr:
|
||||
total = qr[0].total
|
||||
else:
|
||||
total = 0
|
||||
|
||||
return total
|
||||
148
tools/migrate_cluebringer_wblist_to_amavisd.py
Normal file
148
tools/migrate_cluebringer_wblist_to_amavisd.py
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Migrate Cluebringer white/blacklist to Amavisd database.
|
||||
#
|
||||
# Note: it's safe to execute this script as many times as you want, it won't
|
||||
# generate duplicate records.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from libs.iredutils import is_valid_amavisd_address
|
||||
from libs.amavisd import wblist
|
||||
from tools import ira_tool_lib
|
||||
|
||||
web.config.debug = ira_tool_lib.debug
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
# Check database name to make sure it's Cluebringer
|
||||
if settings.policyd_db_name != 'cluebringer':
|
||||
sys.exit('Error: not a Cluebringer database.')
|
||||
|
||||
logger.info('Establish SQL connection.')
|
||||
conn = ira_tool_lib.get_db_conn('policyd')
|
||||
|
||||
logger.info('Query white/blacklist info.')
|
||||
|
||||
# Converted wblist
|
||||
wl = []
|
||||
bl = []
|
||||
|
||||
# value of sql column: policy_groups.id
|
||||
wl_id = None
|
||||
bl_id = None
|
||||
wb_ids = []
|
||||
|
||||
# query whitelist and/or blacklist. possible values: 'wl', 'bl'.
|
||||
query_lists = []
|
||||
|
||||
# get policy_groups.id
|
||||
qr = conn.select('policy_groups', what='id,name', where="name IN ('whitelists', 'blacklists')")
|
||||
if qr:
|
||||
for r in qr:
|
||||
if r.name == 'whitelists':
|
||||
wl_id = r.id
|
||||
elif r.name == 'blacklists':
|
||||
bl_id = r.id
|
||||
|
||||
if wl_id:
|
||||
logger.info('policy_groups.id: %d -> whitelists' % wl_id)
|
||||
query_lists.append('wl')
|
||||
wb_ids.append(wl_id)
|
||||
|
||||
if bl_id:
|
||||
logger.info('policy_groups.id: %d -> blacklists' % bl_id)
|
||||
query_lists.append('bl')
|
||||
wb_ids.append(bl_id)
|
||||
else:
|
||||
logger.info('No whitelist/blacklist found. Exit.')
|
||||
sys.exit()
|
||||
|
||||
logger.info('Query all whitelists and blacklists.')
|
||||
qr = conn.select('policy_group_members',
|
||||
vars={'wb_ids': wb_ids},
|
||||
what='policygroupid, member',
|
||||
where='policygroupid IN $wb_ids AND disabled=0')
|
||||
|
||||
if qr:
|
||||
logger.info('Convert Cluebringer white/blacklists to Amavisd syntax format.')
|
||||
for r in qr:
|
||||
# Single IP Address: 192.168.2.10
|
||||
# CIDR formatted range of IP addresses: 192.168.2.10/31
|
||||
# Single user: user@example.com
|
||||
# Entire domain: @example.com
|
||||
# All sub-domains: .example.com
|
||||
value = None
|
||||
if is_valid_amavisd_address(r.member):
|
||||
value = r.member
|
||||
else:
|
||||
# Convert from different syntax format
|
||||
if r.member.startswith('.'):
|
||||
tmp = '@' + r.member
|
||||
if is_valid_amavisd_address(tmp):
|
||||
value = tmp
|
||||
else:
|
||||
logger.info('[?] Discard record in improper format: %s, cannot convert.' % r.member)
|
||||
elif '/' in r.member:
|
||||
logger.info('[?] Discard record in improper format: %s. CIDR IP range is not supported.' % r.member)
|
||||
|
||||
if value:
|
||||
if r.policygroupid == wl_id:
|
||||
wl.append(value)
|
||||
else:
|
||||
bl.append(value)
|
||||
|
||||
if wl:
|
||||
logger.info('Converted whitelisted: %d total' % len(wl))
|
||||
else:
|
||||
logger.info('No whitelists found.')
|
||||
|
||||
if bl:
|
||||
logger.info('Converted blacklisted: %d total' % len(bl))
|
||||
else:
|
||||
logger.info('No blacklists found.')
|
||||
|
||||
confirm = input('Migrate converted white/blacklists to Amavisd database right now? [y|N]')
|
||||
if confirm not in ['y', 'Y', 'yes', 'YES']:
|
||||
logger.info('Exit without migrating to Amavisd database.')
|
||||
sys.exit()
|
||||
|
||||
# Import to Amavisd database.
|
||||
try:
|
||||
logger.info('Migrating, please wait ...')
|
||||
wblist.add_wblist(account='@.',
|
||||
wl_senders=wl,
|
||||
bl_senders=bl,
|
||||
flush_before_import=False)
|
||||
|
||||
logger.info("Don't forget to enable iRedAPD plugin 'amavisd_wblist' in /opt/iredapd/settings.py.")
|
||||
except Exception as e:
|
||||
logger.info(str(e))
|
||||
|
||||
# Ask to delete wblist in cluebringer
|
||||
confirm = input('Delete all white/blacklists stored in Cluebringer database? [y|N]')
|
||||
if confirm not in ['y', 'Y', 'yes', 'YES']:
|
||||
logger.info('Exit without deleting Cluebringer white/blacklists.')
|
||||
sys.exit()
|
||||
|
||||
conn.delete('policy_group_members', vars={'wb_ids': wb_ids}, where='policygroupid IN $wb_ids')
|
||||
conn.delete('policy_groups', vars={'wb_ids': wb_ids}, where='id IN $wb_ids')
|
||||
conn.delete('policy_members', where="destination='%%internal_domains' AND source IN ('%%whitelists', '%%blacklists')")
|
||||
|
||||
# Get policies.id
|
||||
qr = conn.select('policies', what='id', where="name IN ('whitelists', 'blacklists')")
|
||||
if qr:
|
||||
pids = [r.id for r in qr]
|
||||
|
||||
conn.delete('access_control', vars={'pids': pids}, where='policyid IN $pids')
|
||||
conn.delete('policies', vars={'pids': pids}, where='id IN $pids')
|
||||
|
||||
logger.info('DONE')
|
||||
31
tools/notify_quarantined_recipients.html
Normal file
31
tools/notify_quarantined_recipients.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.th_size, .th_date, .td_size, .td_date { white-space: nowrap; }
|
||||
.tr_date { background-color: #DDDDDD; }
|
||||
.text_align_left { text-align: left; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>Quarantined mails will be kept for %(quar_keep_days)d days, please login to self-service site to manage them: <a href="%(iredadmin_url)s" target="_blank" rel='noopener'>%(iredadmin_url)s</a>.</p>
|
||||
|
||||
<p>Date and time are in time zone: %(timezone)s.</p>
|
||||
|
||||
<table cellpadding="2px">
|
||||
<thead>
|
||||
<th class="th_subject">Subject</th>
|
||||
<th class="th_sender">Sender</th>
|
||||
<th class="th_spam_level">Spam Level</th>
|
||||
<th class="th_date">Time</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Info of quarantined mails -->
|
||||
%(quar_mail_info)s
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
369
tools/notify_quarantined_recipients.py
Normal file
369
tools/notify_quarantined_recipients.py
Normal file
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Notify local recipients (via email) that they have emails
|
||||
# quarantined on server and not delivered to their mailbox.
|
||||
|
||||
# Usage:
|
||||
#
|
||||
# - Set a correct URL in iRedAdmin-Pro config file `settings.py`, so that
|
||||
# users can manage quarantined email within received notification email:
|
||||
#
|
||||
# # URL of your iRedAdmin-Pro login page which will be shown in notification
|
||||
# # email, so that user can login to manage quarantined emails.
|
||||
# # Sample: 'https://your_server.com/iredadmin/'
|
||||
# #
|
||||
# # Note: mail domain must have self-service enabled, otherwise normal
|
||||
# # mail user cannot login to iRedAdmin-Pro for self-service.
|
||||
# NOTIFICATION_URL_SELF_SERVICE = 'https://[your_server]/iredadmin/'
|
||||
#
|
||||
# - Setup a cron job to run this script every 6 or 12, 24 hours, it's up to
|
||||
# you. Sample cron job (every 12 hours):
|
||||
#
|
||||
# 1 */12 * * * python /path/to/notify_quarantined_recipients.py >/dev/null
|
||||
#
|
||||
# Available arguments:
|
||||
#
|
||||
# --force-all:
|
||||
# Send notification to all users (who have email quarantined).
|
||||
#
|
||||
# --force-all-time:
|
||||
# Notify users for their all quarantined emails instead of just new
|
||||
# ones since last notification.
|
||||
#
|
||||
# --notify-backupmx
|
||||
# Send notification to all recipients under backup mx domain
|
||||
#
|
||||
# - Also, it's ok to run this script manually:
|
||||
#
|
||||
# # python notify_quarantined_recipients.py [arg1 arg2 arg3 ...]
|
||||
|
||||
# Customization
|
||||
#
|
||||
# - This script sends email via /usr/sbin/sendmail command by default, it
|
||||
# should work quite well and has better performance. if you still prefer
|
||||
# to send notification email via smtp, please set proper smtp server and
|
||||
# account info in iRedAdmin-Pro config file `settings.py`:
|
||||
#
|
||||
# NOTIFICATION_SMTP_SERVER = 'localhost'
|
||||
# NOTIFICATION_SMTP_PORT = 587
|
||||
# NOTIFICATION_SMTP_STARTTLS = True
|
||||
# NOTIFICATION_SMTP_USER = ''
|
||||
# NOTIFICATION_SMTP_PASSWORD = ''
|
||||
#
|
||||
# - To custom mail subject of notification email, please define below
|
||||
# variable in iRedAdmin-Pro config file `settings.py`:
|
||||
#
|
||||
# # Subject of notification email.
|
||||
# NOTIFICATION_QUARANTINE_MAIL_SUBJECT = '[Attention] You have emails quarantined and not delivered to mailbox'
|
||||
#
|
||||
# - To custom HTML template file, please create your own template file with
|
||||
# correct name in either place:
|
||||
#
|
||||
# - `/opt/iredmail/custom/iredadmin/notify_quarantined_recipients.html`
|
||||
#
|
||||
# This file is used if your iRedMail server was deployed with the
|
||||
# iRedMail Easy platform (https://www.iredmail.org/easy.html), easy
|
||||
# for iRedAdmin-Pro upgrade.
|
||||
#
|
||||
# - `tools/notify_quarantined_recipients.html.custom` under iRedAdmin-Pro
|
||||
# directory.
|
||||
#
|
||||
# General use. Note: there's a `.custom` suffix in file name.
|
||||
#
|
||||
# If no custom file, `tools/notify_quarantined_recipients.html` will be used.
|
||||
#
|
||||
# How it works:
|
||||
#
|
||||
# - Mail user login to iRedAdmin-Pro (self-service) and choose to receive
|
||||
# notification email when there's email quarantined.
|
||||
#
|
||||
# - OpenLDAP: user will be assigned `enabledService=quar_notify`.
|
||||
# - SQL backends: column `mailbox.settings` contains `quar_notify:yes`.
|
||||
#
|
||||
# - This script queries SQL/LDAP database to see who are willing to receive
|
||||
# a notification email.
|
||||
#
|
||||
# - This script checks Amavisd database to get info of quarantined mails
|
||||
# for these users.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.header import Header
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
script_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rootdir = script_dir + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
now = int(time.time())
|
||||
|
||||
import settings
|
||||
from libs import iredutils
|
||||
from libs.ireddate import utc_to_timezone
|
||||
from tools import ira_tool_lib
|
||||
|
||||
web.config.debug = ira_tool_lib.debug
|
||||
logger = ira_tool_lib.logger
|
||||
|
||||
backend = settings.backend
|
||||
|
||||
# Read template HTML file.
|
||||
custom_easy_tmpl = "/opt/iredmail/custom/iredadmin/notify_quarantined_recipients.html"
|
||||
custom_tmpl = os.path.join(rootdir, 'tools', 'notify_quarantined_recipients.html.custom')
|
||||
default_tmpl = os.path.join(rootdir, 'tools', 'notify_quarantined_recipients.html')
|
||||
|
||||
if os.path.isfile(custom_easy_tmpl):
|
||||
html_tmpl = custom_easy_tmpl
|
||||
elif os.path.isfile(custom_tmpl):
|
||||
html_tmpl = custom_tmpl
|
||||
else:
|
||||
html_tmpl = default_tmpl
|
||||
|
||||
# Info used in notification email.
|
||||
mail_subject = settings.NOTIFICATION_QUARANTINE_MAIL_SUBJECT
|
||||
smtp_user = settings.NOTIFICATION_SMTP_USER
|
||||
iredadmin_url = settings.NOTIFICATION_URL_SELF_SERVICE
|
||||
|
||||
# Use '--force-all' option to notify all mail users.
|
||||
force_all_users = '--force-all' in sys.argv or False
|
||||
force_all_time = '--force-all-time' in sys.argv or False
|
||||
notify_backupmx = '--notify-backupmx' in sys.argv or False
|
||||
|
||||
# Backup MX domains.
|
||||
# We may not have any accounts under backup mx domain, so if sys admin chooses
|
||||
# to notify recipients in backup mx domain, we send the notification also.
|
||||
backupmx_domains = []
|
||||
|
||||
# List of target users' email address.
|
||||
target_users = []
|
||||
|
||||
# Get list of users (email) who asked to receive notification email.
|
||||
if settings.backend == 'ldap':
|
||||
from libs.ldaplib.core import LDAPWrap
|
||||
_wrap = LDAPWrap()
|
||||
conn_ldap = _wrap.conn
|
||||
|
||||
# Get users who ask to get a notification email under each domain.
|
||||
if force_all_users:
|
||||
q_filter = '(&(objectClass=mailUser)(accountStatus=active))'
|
||||
else:
|
||||
q_filter = '(&(objectClass=mailUser)(accountStatus=active)(enabledService=quar_notify))'
|
||||
|
||||
try:
|
||||
qr = conn_ldap.search_s(settings.ldap_basedn,
|
||||
2, # ldap.SCOPE_SUBTREE,
|
||||
q_filter,
|
||||
['mail'])
|
||||
for (_dn, _ldif) in qr:
|
||||
_ldif = iredutils.bytes2str(_ldif)
|
||||
target_users += _ldif.get('mail', [])
|
||||
except Exception as e:
|
||||
logger.info('<< ERROR >> Error while querying mail users: %s' % repr(e))
|
||||
|
||||
if notify_backupmx:
|
||||
# Query all backup mx domains
|
||||
q_filter = '(&(objectClass=mailDomain)(accountStatus=active)(domainBackupMX=yes)(mtaTransport=relay:*))'
|
||||
|
||||
try:
|
||||
qr = conn_ldap.search_s(settings.ldap_basedn,
|
||||
1, # ldap.SCOPE_ONELEVEL,
|
||||
q_filter,
|
||||
['domainName', 'domainAliasName'])
|
||||
for (_dn, _ldif) in qr:
|
||||
_ldif = iredutils.bytes2str(_ldif)
|
||||
backupmx_domains += _ldif.get('domainName', [])
|
||||
backupmx_domains += _ldif.get('domainAliasName', [])
|
||||
except Exception as e:
|
||||
logger.info('<< ERROR >> Error while querying backup MX domains: %s' % repr(e))
|
||||
|
||||
elif settings.backend in ['mysql', 'pgsql']:
|
||||
conn_vmaildb = ira_tool_lib.get_db_conn('vmail')
|
||||
|
||||
# Get all users who asked to receive notification email.
|
||||
if force_all_users:
|
||||
sql_where = 'active=1'
|
||||
else:
|
||||
sql_where = 'settings LIKE %s AND active=1' % web.sqlquote('%' + 'quar_notify:' + '%')
|
||||
|
||||
qr = conn_vmaildb.select('mailbox',
|
||||
what='username',
|
||||
where=sql_where)
|
||||
|
||||
for r in qr:
|
||||
target_users.append(r.username)
|
||||
|
||||
if notify_backupmx:
|
||||
# Get all backup mx domains
|
||||
qr = conn_vmaildb.select('domain',
|
||||
what='domain',
|
||||
where='backupmx=1 AND active=1')
|
||||
for i in qr:
|
||||
backupmx_domains += [str(i.domain).lower()]
|
||||
|
||||
if backupmx_domains:
|
||||
# Get all alias domains
|
||||
qr = conn_vmaildb.select('alias_domain',
|
||||
vars={'domains': backupmx_domains},
|
||||
what='alias_domain',
|
||||
where='target_domain IN $domains')
|
||||
|
||||
for i in qr:
|
||||
backupmx_domains += [str(i.alias_domain).lower()]
|
||||
|
||||
if not (target_users or backupmx_domains):
|
||||
logger.debug('No user asks to receive notification email. Exit.')
|
||||
sys.exit()
|
||||
|
||||
mail_body_template = open(html_tmpl).read()
|
||||
|
||||
conn_amavisd = ira_tool_lib.get_db_conn('amavisd')
|
||||
conn_iredadmin = ira_tool_lib.get_db_conn('iredadmin')
|
||||
|
||||
reversed_backupmx_domains = []
|
||||
target_backupmx_users = []
|
||||
if backupmx_domains:
|
||||
for d in backupmx_domains:
|
||||
rd = d.split('.')
|
||||
rd.reverse()
|
||||
rd = '.'.join(rd)
|
||||
|
||||
reversed_backupmx_domains.append(rd)
|
||||
|
||||
qr = conn_amavisd.select('maddr',
|
||||
vars={'rcpt': reversed_backupmx_domains},
|
||||
what='email',
|
||||
where='domain IN $rcpt')
|
||||
for i in qr:
|
||||
_email = iredutils.bytes2str(i.email)
|
||||
target_backupmx_users.append(_email)
|
||||
|
||||
logger.info('%d backup MX domains (%d users) will receive notification email.' % (len(backupmx_domains), len(target_backupmx_users)))
|
||||
|
||||
logger.debug('%d users are willing to receive notification email.' % len(target_users))
|
||||
|
||||
target_users += target_backupmx_users
|
||||
|
||||
# Notify users.
|
||||
for user in target_users:
|
||||
# Get maddr.id of recipient
|
||||
qr = conn_amavisd.select('maddr',
|
||||
vars={'rcpt': user},
|
||||
what='id',
|
||||
where='email=$rcpt',
|
||||
limit=1)
|
||||
if qr:
|
||||
rid = qr[0].id
|
||||
else:
|
||||
logger.debug('[SKIP] No log of user: ' + user)
|
||||
continue
|
||||
|
||||
# Get info of quarantined mails
|
||||
sql_what = 'msgrcpt.rid AS rid,' \
|
||||
+ 'msgs.mail_id AS mail_id,' \
|
||||
+ 'msgs.subject AS subject,' \
|
||||
+ 'msgs.from_addr AS from_addr,' \
|
||||
+ 'msgs.spam_level AS spam_level,' \
|
||||
+ 'msgs.time_num'
|
||||
|
||||
sql_where = """msgrcpt.rid=$rid AND msgs.mail_id=msgrcpt.mail_id AND msgs.quar_type='Q'"""
|
||||
|
||||
last_notify_time = 0
|
||||
if not force_all_time:
|
||||
# Get last time
|
||||
try:
|
||||
qr = conn_iredadmin.select('tracking', what='v', where="k='quarantine_notify_time'", limit=1)
|
||||
if qr:
|
||||
last_notify_time = int(qr[0].v) or 0
|
||||
except:
|
||||
pass
|
||||
|
||||
if last_notify_time:
|
||||
sql_where += """ AND msgs.time_num >= %s""" % last_notify_time
|
||||
|
||||
qr = conn_amavisd.select(['msgs', 'msgrcpt'],
|
||||
vars={'rid': rid},
|
||||
what=sql_what,
|
||||
where=sql_where,
|
||||
order='msgs.time_num DESC')
|
||||
|
||||
if not qr:
|
||||
logger.debug('[SKIP] No quarantined emails for %s.' % user)
|
||||
continue
|
||||
|
||||
total = len(qr)
|
||||
|
||||
# Group messages by date.
|
||||
info_by_date = {}
|
||||
|
||||
quar_mail_info = '\n'
|
||||
|
||||
# Create a HTML table to present quarantined emails.
|
||||
for rcd in qr:
|
||||
# time format: Apr 4, 2015
|
||||
dt = iredutils.epoch_seconds_to_gmt(iredutils.bytes2str(rcd.time_num))
|
||||
time_with_tz = utc_to_timezone(dt=dt, timezone=settings.LOCAL_TIMEZONE)
|
||||
try:
|
||||
time_tuple = time_with_tz.timetuple()
|
||||
except:
|
||||
time_tuple = time.strptime(time_with_tz, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
mail_date = time.strftime('%b %d, %Y', time_tuple)
|
||||
mail_time = time.strftime('%H:%M:%S', time_tuple)
|
||||
|
||||
info = '<tr>' + '\n'
|
||||
info += '<td class="td_subject">' + iredutils.bytes2str(rcd.subject) + '</td>' + '\n'
|
||||
info += '<td class="td_sender">' + iredutils.bytes2str(rcd.from_addr) + '</td>' + '\n'
|
||||
info += '<td class="td_spam_level">' + iredutils.bytes2str(rcd.spam_level) + '</td>' + '\n'
|
||||
info += '<td class="td_date">' + mail_time + '</td>' + '\n'
|
||||
info += '</tr>' + '\n\n'
|
||||
|
||||
if mail_date not in info_by_date:
|
||||
info_by_date[mail_date] = []
|
||||
|
||||
info_by_date[mail_date].append(info)
|
||||
|
||||
for _date in sorted(list(info_by_date.keys()), reverse=True):
|
||||
quar_mail_info += '<tr class="tr_date"><td colspan="4">' + _date + '</td></tr>' + '\n'
|
||||
for r in info_by_date[_date]:
|
||||
quar_mail_info += r
|
||||
|
||||
msg = MIMEMultipart('alternative')
|
||||
|
||||
msg['Subject'] = Header(mail_subject % {'total': total}, 'utf-8')
|
||||
msg['To'] = user
|
||||
|
||||
if settings.NOTIFICATION_SENDER_NAME:
|
||||
msg['From'] = '{} <{}>'.format(Header(settings.NOTIFICATION_SENDER_NAME, 'utf-8'), smtp_user)
|
||||
else:
|
||||
msg['From'] = Header(smtp_user, 'utf-8')
|
||||
|
||||
mail_body = mail_body_template % {'quar_mail_info': quar_mail_info,
|
||||
'quar_keep_days': settings.AMAVISD_REMOVE_QUARANTINED_IN_DAYS,
|
||||
'iredadmin_url': iredadmin_url,
|
||||
'timezone': settings.LOCAL_TIMEZONE}
|
||||
|
||||
# HTML email must contain text and html part with same content, otherwise
|
||||
# it will be considered as not well-formated email.
|
||||
body_part_plain = MIMEText(mail_body, 'plain', 'utf-8')
|
||||
msg.attach(body_part_plain)
|
||||
|
||||
body_part_html = MIMEText(mail_body, 'html', 'utf-8')
|
||||
msg.attach(body_part_html)
|
||||
|
||||
msg_string = msg.as_string()
|
||||
|
||||
ret = iredutils.sendmail(recipients=user, message_text=msg_string)
|
||||
if ret[0]:
|
||||
logger.info('+ %s: %d mails.' % (user, total))
|
||||
else:
|
||||
logger.info('+ << ERROR >> Error while sending notification email to {}: {}'.format(user, ret[1]))
|
||||
|
||||
# Log last notify time.
|
||||
conn_iredadmin.delete('tracking', where="k='quarantine_notify_time'")
|
||||
conn_iredadmin.insert('tracking', k='quarantine_notify_time', v=now)
|
||||
73
tools/promote_user_to_global_admin.py
Normal file
73
tools/promote_user_to_global_admin.py
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Promote given user to be a global admin.
|
||||
# FYI https://docs.iredmail.org/promote.user.to.be.global.admin.html
|
||||
# Usage:
|
||||
# python3 promote_to_global_admin.py <user-email>
|
||||
|
||||
|
||||
def usage():
|
||||
print("""Usage: Run this script with user email address:
|
||||
|
||||
# python3 promote_to_global_admin.py user@domain.com
|
||||
""")
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from tools.ira_tool_lib import debug, get_db_conn
|
||||
from libs.iredutils import is_email
|
||||
|
||||
backend = settings.backend
|
||||
web.config.debug = debug
|
||||
|
||||
# Check arguments
|
||||
if len(sys.argv) == 2:
|
||||
email = sys.argv[1]
|
||||
|
||||
if not is_email(email):
|
||||
usage()
|
||||
sys.exit()
|
||||
else:
|
||||
usage()
|
||||
sys.exit()
|
||||
|
||||
if backend == 'ldap':
|
||||
from libs.ldaplib.core import LDAPWrap
|
||||
from libs.ldaplib import ldaputils
|
||||
_wrap = LDAPWrap()
|
||||
conn = _wrap.conn
|
||||
|
||||
dn = ldaputils.rdn_value_to_user_dn(email)
|
||||
mod_attrs = ldaputils.attr_ldif(attr="enabledService", value="domainadmin", mode="add")
|
||||
mod_attrs += ldaputils.attr_ldif(attr="domainGlobalAdmin", value="yes", mode="add")
|
||||
|
||||
try:
|
||||
conn.modify_s(dn, mod_attrs)
|
||||
print("User {} is now a global admin.".format(email))
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(repr(e)))
|
||||
|
||||
elif backend in ['mysql', 'pgsql']:
|
||||
conn = get_db_conn('vmail')
|
||||
try:
|
||||
conn.update("mailbox",
|
||||
isadmin=1,
|
||||
isglobaladmin=1,
|
||||
where="username='{}'".format(email))
|
||||
|
||||
conn.insert("domain_admins",
|
||||
username=email,
|
||||
domain="ALL")
|
||||
|
||||
print("User {} is now a global admin.".format(email))
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(repr(e)))
|
||||
70
tools/reset_user_password.py
Normal file
70
tools/reset_user_password.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Update user password.
|
||||
# Usage:
|
||||
# python reset_user_password.py <email> <new_password>
|
||||
|
||||
|
||||
def usage():
|
||||
print("""Usage: Run this script with user email address and new plain password:
|
||||
|
||||
# python3 reset_user_password.py user@domain.com 123456
|
||||
""")
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from tools.ira_tool_lib import debug, get_db_conn
|
||||
from libs.iredutils import is_email
|
||||
from libs.iredpwd import generate_password_hash
|
||||
|
||||
backend = settings.backend
|
||||
web.config.debug = debug
|
||||
|
||||
# Check arguments
|
||||
if len(sys.argv) == 3:
|
||||
email = sys.argv[1]
|
||||
pw = sys.argv[2]
|
||||
|
||||
if not is_email(email):
|
||||
usage()
|
||||
sys.exit()
|
||||
else:
|
||||
usage()
|
||||
sys.exit()
|
||||
|
||||
pw_hash = generate_password_hash(pw)
|
||||
if backend == 'ldap':
|
||||
from libs.ldaplib.core import LDAPWrap
|
||||
from libs.ldaplib import ldaputils
|
||||
_wrap = LDAPWrap()
|
||||
conn = _wrap.conn
|
||||
|
||||
dn = ldaputils.rdn_value_to_user_dn(email)
|
||||
mod_attrs = ldaputils.mod_replace('userPassword', pw_hash)
|
||||
mod_attrs += ldaputils.mod_replace('shadowLastChange', ldaputils.get_days_of_shadow_last_change())
|
||||
|
||||
try:
|
||||
conn.modify_s(dn, mod_attrs)
|
||||
print("[{}] Password has been reset.".format(email))
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(repr(e)))
|
||||
elif backend in ['mysql', 'pgsql']:
|
||||
conn = get_db_conn('vmail')
|
||||
try:
|
||||
conn.update('mailbox',
|
||||
password=pw_hash,
|
||||
passwordlastchange=web.sqlliteral('NOW()'),
|
||||
where="username='{}'".format(email))
|
||||
|
||||
print("[{}] Password has been reset.".format(email))
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(repr(e)))
|
||||
106
tools/update_mailbox_quota.py
Normal file
106
tools/update_mailbox_quota.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Update mailbox quota for one user or multiple users.
|
||||
# Note: Mailbox quota size unit is bytes. for example, size `104857600` is 100 MB.
|
||||
|
||||
|
||||
def usage():
|
||||
print("""Usage:
|
||||
|
||||
1) Update mailbox quota for one user.
|
||||
|
||||
To simply update one user's quota, run this script with user's email
|
||||
address and new quota size (in bytes). For example:
|
||||
|
||||
# python3 update_mailbox_quota.py user@domain.com 2048576000
|
||||
|
||||
2) Update mailbox quota for multiple users.
|
||||
|
||||
- Create text file "new_quota.txt", each line contains one email address
|
||||
and the new quota size (in bytes).
|
||||
|
||||
user1@domain.com 20480000
|
||||
user2@domain.com 102400000
|
||||
user3@domain.com 409600000
|
||||
|
||||
- Run this script with this file:
|
||||
|
||||
# python3 update_mailbox_quota.py new_quota.txt
|
||||
""")
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from tools.ira_tool_lib import debug, logger, get_db_conn
|
||||
from libs.iredutils import is_email
|
||||
|
||||
backend = settings.backend
|
||||
logger.info('Backend: {}'.format(backend))
|
||||
|
||||
web.config.debug = debug
|
||||
|
||||
# List of (email, quota) tuples.
|
||||
users = []
|
||||
|
||||
# Check arguments
|
||||
if len(sys.argv) == 2:
|
||||
# bulk update
|
||||
text_file = sys.argv[1]
|
||||
if not os.path.isfile(text_file):
|
||||
sys.exit('<<< ERROR>>> Not a regular file: %s' % text_file)
|
||||
|
||||
# Get all (email, quota) tuples.
|
||||
f = open(text_file)
|
||||
for _line in f.readlines():
|
||||
(_email, _quota) = _line.strip().split(' ', 1)
|
||||
if is_email(_email) and _quota.isdigit():
|
||||
users += [(_email, _quota)]
|
||||
else:
|
||||
print("[SKIP] no valid email address or quota: {}".format(_line))
|
||||
|
||||
elif len(sys.argv) == 3:
|
||||
# update single user
|
||||
_email = sys.argv[1]
|
||||
_quota = sys.argv[2]
|
||||
|
||||
if is_email(_email):
|
||||
users += [(_email, _quota)]
|
||||
else:
|
||||
sys.exit('<<< ERROR >>> Not an valid email address: %s' % _email)
|
||||
else:
|
||||
usage()
|
||||
|
||||
total = len(users)
|
||||
logger.info('{} users in total.'.format(total))
|
||||
|
||||
count = 1
|
||||
if backend == 'ldap':
|
||||
from libs.ldaplib.core import LDAPWrap
|
||||
from libs.ldaplib.ldaputils import rdn_value_to_user_dn, mod_replace
|
||||
_wrap = LDAPWrap()
|
||||
conn = _wrap.conn
|
||||
|
||||
for (_email, _quota) in users:
|
||||
logger.info('(%d/%d) Updating %s -> %s' % (count, total, _email, _quota))
|
||||
dn = rdn_value_to_user_dn(_email)
|
||||
mod_attrs = mod_replace('mailQuota', _quota)
|
||||
try:
|
||||
conn.modify_s(dn, mod_attrs)
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(e))
|
||||
elif backend in ['mysql', 'pgsql']:
|
||||
conn = get_db_conn('vmail')
|
||||
for (_email, _quota) in users:
|
||||
logger.info('(%d/%d) Updating %s -> %s' % (count, total, _email, _quota))
|
||||
conn.update('mailbox',
|
||||
quota=int(_quota),
|
||||
where="username='%s'" % _email)
|
||||
104
tools/update_password_in_csv.py
Normal file
104
tools/update_password_in_csv.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
# Purpose: Update user passwords from records in a CSV file.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import web
|
||||
|
||||
|
||||
def usage():
|
||||
print("""Usage:
|
||||
|
||||
- Store the email address and new password in a plain text file, e.g.
|
||||
'passwords.csv'. format is:
|
||||
|
||||
<email> <new_password>
|
||||
|
||||
Samples:
|
||||
|
||||
user1@domain.com pF4mTq4jaRzDLlWl
|
||||
user2@domain.com SPhkTUlZs1TBxvmJ
|
||||
user3@domain.com 8deNR8IBLycRujDN
|
||||
|
||||
- Run this script with this file:
|
||||
|
||||
python3 update_password_in_csv.py passwords.csv
|
||||
""")
|
||||
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
|
||||
rootdir = os.path.abspath(os.path.dirname(__file__)) + '/../'
|
||||
sys.path.insert(0, rootdir)
|
||||
|
||||
import settings
|
||||
from tools.ira_tool_lib import debug, logger, get_db_conn
|
||||
from libs.iredutils import is_email
|
||||
from libs.iredpwd import generate_password_hash
|
||||
|
||||
backend = settings.backend
|
||||
logger.info('Backend: %s' % backend)
|
||||
|
||||
web.config.debug = debug
|
||||
|
||||
logger.info('Parsing command line arguments.')
|
||||
|
||||
# File which stores email and quota.
|
||||
text_file = ''
|
||||
|
||||
# The separator
|
||||
column_separator = ' '
|
||||
|
||||
# List of (email, quota) tuples.
|
||||
users = []
|
||||
|
||||
# Check arguments
|
||||
if len(sys.argv) == 2:
|
||||
text_file = sys.argv[1]
|
||||
if not os.path.isfile(text_file):
|
||||
sys.exit('<<< ERROR>>> Not a regular file: %s' % text_file)
|
||||
|
||||
# Get all (email, password) tuples.
|
||||
f = open(text_file)
|
||||
line_num = 0
|
||||
for _line in f.readlines():
|
||||
line_num += 1
|
||||
(_email, _pw) = _line.split(column_separator, 1)
|
||||
if is_email(_email):
|
||||
users += [(_email, _pw)]
|
||||
else:
|
||||
print("[SKIP] line {}: no valid email address: {}".format(line_num, _line))
|
||||
f.close()
|
||||
else:
|
||||
usage()
|
||||
|
||||
total = len(users)
|
||||
logger.info('%d users in total.' % total)
|
||||
|
||||
count = 1
|
||||
if backend == 'ldap':
|
||||
from libs.ldaplib.core import LDAPWrap
|
||||
from libs.ldaplib.ldaputils import rdn_value_to_user_dn, mod_replace
|
||||
_wrap = LDAPWrap()
|
||||
conn = _wrap.conn
|
||||
|
||||
for (_email, _pw) in users:
|
||||
logger.info('(%d/%d) Updating %s' % (count, total, _email))
|
||||
|
||||
dn = rdn_value_to_user_dn(_email)
|
||||
pw_hash = generate_password_hash(_pw)
|
||||
mod_attrs = mod_replace('userPassword', pw_hash)
|
||||
try:
|
||||
conn.modify_s(dn, mod_attrs)
|
||||
except Exception as e:
|
||||
print("<<< ERROR >>> {}".format(repr(e)))
|
||||
elif backend in ['mysql', 'pgsql']:
|
||||
conn = get_db_conn('vmail')
|
||||
for (_email, _pw) in users:
|
||||
logger.info('(%d/%d) Updating %s' % (count, total, _email))
|
||||
pw_hash = generate_password_hash(_pw)
|
||||
conn.update('mailbox',
|
||||
password=pw_hash,
|
||||
where="username='%s'" % _email)
|
||||
963
tools/upgrade_iredadmin.sh
Normal file
963
tools/upgrade_iredadmin.sh
Normal file
@@ -0,0 +1,963 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Purpose: Upgrade iRedAdmin from old release.
|
||||
# Works with both iRedAdmin open source edition or iRedAdmin-Pro.
|
||||
|
||||
# USAGE:
|
||||
#
|
||||
# # cd /path/to/iRedAdmin-xxx/tools/
|
||||
# # bash upgrade_iredadmin.sh
|
||||
#
|
||||
# Notes:
|
||||
#
|
||||
# * it uses sql username 'root' by default to connect to sql database. If you
|
||||
# are using a remote SQL database which you don't have root privilege,
|
||||
# please specify the sql username on command line with 'SQL_IREDADMIN_USER'
|
||||
# parameter like this:
|
||||
#
|
||||
# SQL_IREDADMIN_USER='iredadmin' bash upgrade_iredadmin.sh
|
||||
#
|
||||
# * it reads sql password for given sql user from /root/.my.cnf by default.
|
||||
# if you use a different file, please specify the file on command line with
|
||||
# 'MY_CNF' parameter like this:
|
||||
#
|
||||
# MY_CNF='/root/.my.cnf-iredadmin' SQL_IREDADMIN_USER='iredadmin' bash upgrade_iredadmin.sh
|
||||
|
||||
export LC_ALL='C'
|
||||
export IRA_HTTPD_USER='iredadmin'
|
||||
export IRA_HTTPD_GROUP='iredadmin'
|
||||
|
||||
export SYS_ROOT_USER='root'
|
||||
|
||||
# If you don't have root privilege, use another sql user instead.
|
||||
export SQL_IREDADMIN_USER="${SQL_IREDADMIN_USER:=root}"
|
||||
export MY_CNF="${MY_CNF:=/root/.my.cnf}"
|
||||
export CMD_MYSQL="mysql --defaults-file=${MY_CNF} -u ${SQL_IREDADMIN_USER}"
|
||||
|
||||
# Check OS to detect some necessary info.
|
||||
export KERNEL_NAME="$(uname -s | tr '[a-z]' '[A-Z]')"
|
||||
|
||||
export NGINX_PID_FILE='/var/run/nginx.pid'
|
||||
export NGINX_SNIPPET_CONF='/etc/nginx/templates/iredadmin.tmpl'
|
||||
export NGINX_SNIPPET_CONF2='/etc/nginx/templates/iredadmin-subdomain.tmpl'
|
||||
# iRedMail-0.9.7
|
||||
export NGINX_SNIPPET_CONF3='/etc/nginx/conf.d/default.conf'
|
||||
|
||||
export USE_SYSTEMD='NO'
|
||||
if which systemctl &>/dev/null; then
|
||||
export USE_SYSTEMD='YES'
|
||||
export SYSTEMD_SERVICE_DIR='/lib/systemd/system'
|
||||
export SYSTEMD_SERVICE_DIR2='/etc/systemd/system'
|
||||
export SYSTEMD_SERVICE_USER_DIR='/etc/systemd/system/multi-user.target.wants/'
|
||||
fi
|
||||
|
||||
# Python.
|
||||
export CMD_PYTHON3='/usr/bin/python3'
|
||||
export CMD_PIP3='/usr/bin/pip3'
|
||||
|
||||
# uwsgi
|
||||
export CMD_UWSGI='/usr/bin/uwsgi'
|
||||
|
||||
# If uwsgi is installed with pip, plugins are compiled into core binary
|
||||
# directly, not plugins are installed separately.
|
||||
# Mainly used on RHEL/CentOS/Rocky/Alma.
|
||||
export UWSGI_HAS_PLUGINS="YES"
|
||||
|
||||
if [ X"${KERNEL_NAME}" == X"LINUX" ]; then
|
||||
# Note: RHEL has minor version number in VERSION_ID.
|
||||
export DISTRO_VERSION=$(awk -F'"' '/^VERSION_ID=/ {print $2}' /etc/os-release | awk -F'.' '{print $1}')
|
||||
|
||||
if [ -f /etc/redhat-release ]; then
|
||||
# RHEL/CentOS
|
||||
export DISTRO='RHEL'
|
||||
|
||||
# Installed with pip.
|
||||
export CMD_UWSGI='/usr/sbin/uwsgi'
|
||||
|
||||
if [[ -x "/usr/local/bin/uwsgi" ]]; then
|
||||
export CMD_UWSGI='/usr/local/bin/uwsgi'
|
||||
export UWSGI_HAS_PLUGINS="NO"
|
||||
fi
|
||||
|
||||
export HTTPD_RC_SCRIPT_NAME='httpd'
|
||||
export CRON_SPOOL_DIR='/var/spool/cron'
|
||||
|
||||
if [[ -L /opt/www/iredadmin ]]; then
|
||||
export HTTPD_SERVERROOT='/opt/www'
|
||||
else
|
||||
export HTTPD_SERVERROOT='/var/www'
|
||||
fi
|
||||
elif [ -f /etc/lsb-release ]; then
|
||||
# Ubuntu
|
||||
export DISTRO='UBUNTU'
|
||||
|
||||
export HTTPD_RC_SCRIPT_NAME='apache2'
|
||||
export CRON_SPOOL_DIR='/var/spool/cron/crontabs'
|
||||
|
||||
if [ -L /opt/www/iredadmin ]; then
|
||||
export HTTPD_SERVERROOT='/opt/www'
|
||||
else
|
||||
export HTTPD_SERVERROOT='/usr/share/apache2'
|
||||
fi
|
||||
elif [ -f /etc/debian_version ]; then
|
||||
# Debian
|
||||
export DISTRO='DEBIAN'
|
||||
|
||||
export HTTPD_RC_SCRIPT_NAME='apache2'
|
||||
export CRON_SPOOL_DIR='/var/spool/cron/crontabs'
|
||||
|
||||
if [ -L /opt/www/iredadmin ]; then
|
||||
export HTTPD_SERVERROOT='/opt/www'
|
||||
else
|
||||
export HTTPD_SERVERROOT='/usr/share/apache2'
|
||||
fi
|
||||
elif [ -f /etc/SuSE-release ]; then
|
||||
# openSUSE
|
||||
export DISTRO='SUSE'
|
||||
export HTTPD_SERVERROOT='/srv/www'
|
||||
export HTTPD_RC_SCRIPT_NAME='apache2'
|
||||
export CRON_SPOOL_DIR='/var/spool/cron'
|
||||
else
|
||||
echo "<<< ERROR >>> Cannot detect Linux distribution name. Exit."
|
||||
echo "Please contact support@iredmail.org to solve it."
|
||||
exit 255
|
||||
fi
|
||||
elif [ X"${KERNEL_NAME}" == X'FREEBSD' ]; then
|
||||
export DISTRO='FREEBSD'
|
||||
export SYSRC='/usr/sbin/sysrc'
|
||||
|
||||
export CMD_PYTHON3='/usr/local/bin/python3'
|
||||
export CMD_UWSGI='/usr/local/bin/uwsgi'
|
||||
|
||||
[ -x /usr/local/bin/pip-3.8 ] && export CMD_PIP3='/usr/local/bin/pip-3.8'
|
||||
[ -x /usr/local/bin/pip3 ] && export CMD_PIP3='/usr/local/bin/pip3'
|
||||
[ -x /usr/local/bin/pip ] && export CMD_PIP3='/usr/local/bin/pip'
|
||||
|
||||
export CRON_SPOOL_DIR='/var/cron/tabs'
|
||||
export NGINX_SNIPPET_CONF='/usr/local/etc/nginx/templates/iredadmin.tmpl'
|
||||
export NGINX_SNIPPET_CONF2='/usr/local/etc/nginx/templates/iredadmin-subdomain.tmpl'
|
||||
export NGINX_SNIPPET_CONF3='/usr/local/etc/nginx/conf.d/default.conf'
|
||||
|
||||
if [ -L /opt/www/iredadmin ]; then
|
||||
export HTTPD_SERVERROOT='/opt/www'
|
||||
else
|
||||
export HTTPD_SERVERROOT='/usr/local/www'
|
||||
fi
|
||||
|
||||
if [ -f /usr/local/etc/rc.d/apache24 ]; then
|
||||
export HTTPD_RC_SCRIPT_NAME='apache24'
|
||||
else
|
||||
export HTTPD_RC_SCRIPT_NAME='apache22'
|
||||
fi
|
||||
elif [ X"${KERNEL_NAME}" == X'OPENBSD' ]; then
|
||||
export CMD_PYTHON3='/usr/local/bin/python3'
|
||||
export CMD_PIP3='/usr/local/bin/pip3'
|
||||
export CMD_UWSGI='/usr/local/bin/uwsgi'
|
||||
export DISTRO='OPENBSD'
|
||||
export CRON_SPOOL_DIR='/var/cron/tabs'
|
||||
|
||||
if [[ -h /opt/www/iredadmin ]]; then
|
||||
export HTTPD_SERVERROOT='/opt/www'
|
||||
else
|
||||
export HTTPD_SERVERROOT='/var/www'
|
||||
fi
|
||||
else
|
||||
echo "Cannot detect Linux/BSD distribution. Exit."
|
||||
echo "Please contact author iRedMail team <support@iredmail.org> to solve it."
|
||||
exit 255
|
||||
fi
|
||||
|
||||
export CRON_FILE_ROOT="${CRON_SPOOL_DIR}/${SYS_ROOT_USER}"
|
||||
|
||||
# Optional argument to set the directory which stores iRedAdmin.
|
||||
if [ $# -gt 0 ]; then
|
||||
if [ -d ${1} ]; then
|
||||
export HTTPD_SERVERROOT="${1}"
|
||||
fi
|
||||
|
||||
if echo ${HTTPD_SERVERROOT} | grep '/iredadmin/*$' > /dev/null; then
|
||||
export HTTPD_SERVERROOT="$(dirname ${HTTPD_SERVERROOT})"
|
||||
fi
|
||||
fi
|
||||
|
||||
# iRedAdmin directory and config file.
|
||||
export IRA_ROOT_DIR="${HTTPD_SERVERROOT}/iredadmin"
|
||||
export IRA_CONF_PY="${IRA_ROOT_DIR}/settings.py"
|
||||
export IRA_CUSTOM_CONF_PY="${IRA_ROOT_DIR}/custom_settings.py"
|
||||
|
||||
enable_service() {
|
||||
srv="$1"
|
||||
|
||||
echo "* Enable service: ${srv}"
|
||||
if [ X"${DISTRO}" == X'RHEL' ]; then
|
||||
if [ X"${USE_SYSTEMD}" == X'YES' ]; then
|
||||
systemctl enable $srv
|
||||
else
|
||||
chkconfig --level 345 $srv on
|
||||
fi
|
||||
elif [ X"${DISTRO}" == X'DEBIAN' -o X"${DISTRO}" == X'UBUNTU' ]; then
|
||||
if [ X"${USE_SYSTEMD}" == X'YES' ]; then
|
||||
systemctl enable $srv
|
||||
else
|
||||
update-rc.d $srv defaults
|
||||
fi
|
||||
elif [ X"${DISTRO}" == X'FREEBSD' ]; then
|
||||
${SYSRC} -f /etc/rc.conf.local ${srv}_enable=YES
|
||||
elif [ X"${DISTRO}" == X'OPENBSD' ]; then
|
||||
rcctl enable $srv
|
||||
fi
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
srv="$1"
|
||||
|
||||
if [ X"${KERNEL_NAME}" == X'LINUX' ]; then
|
||||
if [ X"${USE_SYSTEMD}" == X'YES' ]; then
|
||||
systemctl restart ${srv}
|
||||
else
|
||||
service ${srv} restart
|
||||
fi
|
||||
elif [ X"${KERNEL_NAME}" == X'FREEBSD' ]; then
|
||||
service ${srv} restart
|
||||
elif [ X"${KERNEL_NAME}" == X'OPENBSD' ]; then
|
||||
rcctl restart ${srv}
|
||||
fi
|
||||
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "Failed, please restart service manually and check its log file."
|
||||
fi
|
||||
}
|
||||
|
||||
restart_web_service()
|
||||
{
|
||||
export web_service="${HTTPD_RC_SCRIPT_NAME}"
|
||||
if [ -f ${NGINX_PID_FILE} ]; then
|
||||
if [ -n "$(cat ${NGINX_PID_FILE})" ]; then
|
||||
export web_service="iredadmin"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "* Restarting ${web_service} service."
|
||||
if [ X"${KERNEL_NAME}" == X'LINUX' ]; then
|
||||
# The uwsgi script on CentOS 6 has problem with 'restart' action,
|
||||
# 'stop' with few seconds sleep fixes it.
|
||||
if [ X"${DISTRO}" == X'RHEL' -a X"${web_service}" == X'uwsgi' ]; then
|
||||
service ${web_service} stop
|
||||
sleep 5
|
||||
service ${web_service} start
|
||||
else
|
||||
service ${web_service} restart
|
||||
fi
|
||||
elif [ X"${KERNEL_NAME}" == X'FREEBSD' ]; then
|
||||
service ${web_service} restart
|
||||
elif [ X"${KERNEL_NAME}" == X'OPENBSD' ]; then
|
||||
rcctl restart ${web_service}
|
||||
fi
|
||||
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "Failed, please restart Apache web server or 'iredadmin' (if you're running Nginx as web server) manually."
|
||||
fi
|
||||
}
|
||||
|
||||
check_mlmmjadmin_installation()
|
||||
{
|
||||
if [ ! -e /opt/mlmmjadmin ]; then
|
||||
echo "<<< ERROR >>> No mlmmjadmin installation found (/opt/mlmmjadmin)."
|
||||
echo "<<< ERROR >>> Please follow iRedMail upgrade tutorials to the latest"
|
||||
echo "<<< ERROR >>> stable release first, then come back to upgrade iRedAdmin-Pro."
|
||||
echo "<<< ERROR >>> mlmmj and mlmmjadmin was first introduced in iRedMail-0.9.8."
|
||||
echo "<<< ERROR >>> https://docs.iredmail.org/iredmail.releases.html"
|
||||
exit 255
|
||||
fi
|
||||
}
|
||||
|
||||
remove_pkg() {
|
||||
echo "Remove package(s): $@"
|
||||
if [ X"${DISTRO}" == X'RHEL' ]; then
|
||||
yum remove -y $@
|
||||
fi
|
||||
}
|
||||
|
||||
install_pkg()
|
||||
{
|
||||
echo "Install package(s): $@"
|
||||
|
||||
if [ X"${DISTRO}" == X'RHEL' ]; then
|
||||
yum -y install $@
|
||||
elif [ X"${DISTRO}" == X'DEBIAN' -o X"${DISTRO}" == X'UBUNTU' ]; then
|
||||
apt-get install -y $@
|
||||
elif [ X"${DISTRO}" == X'FREEBSD' ]; then
|
||||
cd /usr/ports/$@ && make install clean
|
||||
elif [ X"${DISTRO}" == X'OPENBSD' ]; then
|
||||
pkg_add -r $@
|
||||
else
|
||||
echo "<< ERROR >> Please install package(s) manually: $@"
|
||||
fi
|
||||
}
|
||||
|
||||
has_python_module()
|
||||
{
|
||||
mod="$1"
|
||||
${CMD_PYTHON3} -c "import $mod" &>/dev/null
|
||||
if [ X"$?" == X'0' ]; then
|
||||
echo 'YES'
|
||||
else
|
||||
echo 'NO'
|
||||
fi
|
||||
}
|
||||
|
||||
add_missing_parameter()
|
||||
{
|
||||
# Usage: add_missing_parameter VARIABLE DEFAULT_VALUE [COMMENT]
|
||||
var="${1}"
|
||||
value="${2}"
|
||||
shift 2
|
||||
comment="$@"
|
||||
|
||||
if ! grep "^${var}" ${IRA_CONF_PY} &>/dev/null; then
|
||||
if [ ! -z "${comment}" ]; then
|
||||
echo "# ${comment}" >> ${IRA_CONF_PY}
|
||||
fi
|
||||
|
||||
if [ X"${value}" == X'True' -o X"${value}" == X'False' ]; then
|
||||
echo "${var} = ${value}" >> ${IRA_CONF_PY}
|
||||
else
|
||||
# Value must be quoted as string.
|
||||
echo "${var} = '${value}'" >> ${IRA_CONF_PY}
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove all single quote and double quotes in string.
|
||||
strip_quotes()
|
||||
{
|
||||
# Read input from stdin
|
||||
str="$(cat <&0)"
|
||||
value="$(echo ${str} | tr -d '"' | tr -d "'")"
|
||||
echo "${value}"
|
||||
}
|
||||
|
||||
get_iredadmin_setting()
|
||||
{
|
||||
var="${1}"
|
||||
value="$(grep "^${var}" ${IRA_CONF_PY} | awk '{print $NF}' | strip_quotes)"
|
||||
echo "${value}"
|
||||
}
|
||||
|
||||
check_dot_my_cnf()
|
||||
{
|
||||
if egrep '^backend.*(mysql|ldap)' ${IRA_CONF_PY} &>/dev/null; then
|
||||
if [ ! -f ${MY_CNF} ]; then
|
||||
echo "<<< ERROR >>> File ${MY_CNF} not found."
|
||||
echo "<<< ERROR >>> Please add mysql root user and password in it like below, then run this script again."
|
||||
cat <<EOF
|
||||
|
||||
[client]
|
||||
host=127.0.0.1
|
||||
port=3306
|
||||
user=${SQL_IREDADMIN_USER}
|
||||
password="plain_password"
|
||||
|
||||
EOF
|
||||
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Check MySQL connection
|
||||
${CMD_MYSQL} -e "SHOW DATABASES" &>/dev/null
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "<<< ERROR >>> MySQL user name '${SQL_IREDADMIN_USER}' or password is incorrect in ${MY_CNF}, please double check."
|
||||
exit 255
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_mlmmjadmin_installation
|
||||
check_dot_my_cnf
|
||||
|
||||
echo "* Detected Linux/BSD distribution: ${DISTRO}"
|
||||
echo "* HTTP server root: ${HTTPD_SERVERROOT}"
|
||||
|
||||
if [ -L ${IRA_ROOT_DIR} ]; then
|
||||
export IRA_ROOT_REAL_DIR="$(readlink ${IRA_ROOT_DIR})"
|
||||
echo "* Found iRedAdmin directory: ${IRA_ROOT_DIR}, symbol link of ${IRA_ROOT_REAL_DIR}"
|
||||
else
|
||||
echo "<<< ERROR >>> Directory (${IRA_ROOT_DIR}) is not a symbol link created by iRedMail. Exit."
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Copy config file
|
||||
if [ -f ${IRA_CONF_PY} ]; then
|
||||
echo "* Found iRedAdmin config file: ${IRA_CONF_PY}"
|
||||
else
|
||||
echo "<<< ERROR >>> Cannot find a valid config file (settings.py)."
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Check whether current directory is iRedAdmin
|
||||
PWD="$(pwd)"
|
||||
if ! echo ${PWD} | grep 'iRedAdmin-.*/tools' >/dev/null; then
|
||||
echo "<<< ERROR >>> Cannot find new version of iRedAdmin in current directory. Exit."
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Copy current directory to Apache server root
|
||||
dir_new_version="$(dirname ${PWD})"
|
||||
name_new_version="$(basename ${dir_new_version})"
|
||||
NEW_IRA_ROOT_DIR="${HTTPD_SERVERROOT}/${name_new_version}"
|
||||
if [ -d ${NEW_IRA_ROOT_DIR} ]; then
|
||||
COPY_FILES="${dir_new_version}/*"
|
||||
COPY_DEST_DIR="${NEW_IRA_ROOT_DIR}"
|
||||
#echo "<<< ERROR >>> Directory exist: ${NEW_IRA_ROOT_DIR}. Exit."
|
||||
#exit 255
|
||||
else
|
||||
COPY_FILES="${dir_new_version}"
|
||||
COPY_DEST_DIR="${HTTPD_SERVERROOT}"
|
||||
fi
|
||||
|
||||
echo "* Copying new version to ${NEW_IRA_ROOT_DIR}"
|
||||
cp -rf ${COPY_FILES} ${COPY_DEST_DIR}
|
||||
|
||||
# Copy old config files
|
||||
echo "* Copy ${IRA_CONF_PY}."
|
||||
cp -p ${IRA_CONF_PY} ${NEW_IRA_ROOT_DIR}/
|
||||
|
||||
if [ -f ${IRA_CUSTOM_CONF_PY} ]; then
|
||||
echo "* Copy ${IRA_CUSTOM_CONF_PY}."
|
||||
cp -p ${IRA_CUSTOM_CONF_PY} ${NEW_IRA_ROOT_DIR}
|
||||
fi
|
||||
|
||||
# Copy hooks.py. It's ok if missing.
|
||||
if [ -f ${IRA_ROOT_DIR}/hooks.py ]; then
|
||||
echo "* Copy ${IRA_ROOT_DIR}/hooks.py."
|
||||
cp -p ${IRA_ROOT_DIR}/hooks.py ${NEW_IRA_ROOT_DIR}/ &>/dev/null
|
||||
fi
|
||||
|
||||
# Copy custom files under 'tools/'. It's ok if missing.
|
||||
cp -p ${IRA_ROOT_DIR}/tools/*.custom ${NEW_IRA_ROOT_DIR}/tools/ &>/dev/null
|
||||
cp -p ${IRA_ROOT_DIR}/tools/*.last-time ${NEW_IRA_ROOT_DIR}/tools/ &>/dev/null
|
||||
|
||||
# Template file renamed
|
||||
if [ -f "${IRA_ROOT_DIR}/tools/notify_quarantined_recipients.custom.html" ]; then
|
||||
echo "* Copy ${IRA_ROOT_DIR}/tools/notify_quarantined_recipients.custom.html"
|
||||
cp -f ${IRA_ROOT_DIR}/tools/notify_quarantined_recipients.custom.html \
|
||||
${NEW_IRA_ROOT_DIR}/tools/notify_quarantined_recipients.html.custom
|
||||
fi
|
||||
|
||||
# Copy favicon.ico and brand logo image.
|
||||
for var in 'BRAND_FAVICON' 'BRAND_LOGO'; do
|
||||
if grep "^${var}\>" ${IRA_CONF_PY} &>/dev/null; then
|
||||
_file=$(grep "^${var}\>" ${IRA_CONF_PY} | awk '{print $NF}' | tr -d '"' | tr -d "'")
|
||||
echo "* Copy file ${IRA_ROOT_DIR}/static/${_file}"
|
||||
cp -f ${IRA_ROOT_DIR}/static/${_file} ${NEW_IRA_ROOT_DIR}/static/
|
||||
fi
|
||||
done
|
||||
|
||||
# iredadmin is now ran as a standalone uwsgi instance, we don't need uwsgi
|
||||
# daemon service anymore.
|
||||
_uwsgi_confs='
|
||||
/etc/uwsgi.d/iredadmin.ini
|
||||
/etc/uwsgi-available/iredadmin.ini
|
||||
/etc/uwsgi/apps-enabled/iredadmin.ini &>/dev/null
|
||||
/etc/uwsgi/apps-available/iredadmin.ini &>/dev/null
|
||||
/usr/local/etc/uwsgi/iredadmin.ini
|
||||
/etc/uwsgi-enabled/iredadmin.ini &>/dev/null
|
||||
/etc/uwsgi-available/iredadmin.ini &>/dev/null
|
||||
'
|
||||
|
||||
for f in ${_uwsgi_confs}; do
|
||||
rm -f ${f} &>/dev/null
|
||||
done
|
||||
|
||||
# Remove 'uwsgi_XXX' from /etc/rc.conf on FreeBSD.
|
||||
if [[ X"${DISTRO}" == X'FREEBSD' ]]; then
|
||||
${SYSRC} -x uwsgi_enable &>/dev/null
|
||||
${SYSRC} -x uwsgi_profiles &>/dev/null
|
||||
${SYSRC} -x uwsgi_iredadmin_flags &>/dev/null
|
||||
fi
|
||||
|
||||
# Update Nginx template file
|
||||
export _restart_nginx='NO'
|
||||
for f in ${NGINX_SNIPPET_CONF} ${NGINX_SNIPPET_CONF2} ${NGINX_SNIPPET_CONF3}; do
|
||||
if [[ -f ${f} ]]; then
|
||||
if grep 'unix:.*iredadmin.socket' ${f} &>/dev/null; then
|
||||
export _restart_nginx='YES'
|
||||
perl -pi -e 's#uwsgi_pass unix:.*iredadmin.socket;#uwsgi_pass 127.0.0.1:7791;#g' ${f}
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ X"${_restart_nginx}" == X'YES' ]]; then
|
||||
restart_service nginx
|
||||
fi
|
||||
|
||||
# Update uwsgi ini config file
|
||||
if [ -d ${NEW_IRA_ROOT_DIR}/rc_scripts/uwsgi ]; then
|
||||
perl -pi -e 's#^chdir = (.*)#chdir = $ENV{HTTPD_SERVERROOT}/iredadmin#g' ${NEW_IRA_ROOT_DIR}/rc_scripts/uwsgi/*.ini
|
||||
fi
|
||||
|
||||
# Copy rc script or systemd service file
|
||||
if [ X"${USE_SYSTEMD}" == X'YES' ]; then
|
||||
echo "* Remove existing systemd service files."
|
||||
rm -f ${SYSTEMD_SERVICE_DIR}/iredadmin.service &>/dev/null
|
||||
rm -f ${SYSTEMD_SERVICE_DIR2}/iredadmin.service &>/dev/null
|
||||
rm -f ${SYSTEMD_SERVICE_USER_DIR}/iredadmin.service &>/dev/null
|
||||
|
||||
echo "* Copy systemd service file: ${SYSTEMD_SERVICE_DIR}/iredadmin.service."
|
||||
if [ X"${DISTRO}" == X'RHEL' ]; then
|
||||
cp -f ${NEW_IRA_ROOT_DIR}/rc_scripts/systemd/rhel${DISTRO_VERSION}.service ${SYSTEMD_SERVICE_DIR}/iredadmin.service
|
||||
perl -pi -e 's#/opt/www#$ENV{HTTPD_SERVERROOT}#g' ${SYSTEMD_SERVICE_DIR}/iredadmin.service
|
||||
perl -pi -e 's#/usr/local/bin/uwsgi#$ENV{CMD_UWSGI}#g' ${SYSTEMD_SERVICE_DIR}/iredadmin.service
|
||||
|
||||
if [[ X"${UWSGI_HAS_PLUGINS}" == X"NO" ]]; then
|
||||
_ini_file="${NEW_IRA_ROOT_DIR}/rc_scripts/uwsgi/rhel${DISTRO_VERSION}.ini"
|
||||
if [[ -f ${_ini_file} ]]; then
|
||||
perl -pi -e 's#^(plugins.*)##g' ${_ini_file}
|
||||
fi
|
||||
fi
|
||||
elif [ X"${DISTRO}" == X'DEBIAN' -o X"${DISTRO}" == X'UBUNTU' ]; then
|
||||
cp -f ${NEW_IRA_ROOT_DIR}/rc_scripts/systemd/debian.service ${SYSTEMD_SERVICE_DIR}/iredadmin.service
|
||||
perl -pi -e 's#/opt/www#$ENV{HTTPD_SERVERROOT}#g' ${SYSTEMD_SERVICE_DIR}/iredadmin.service
|
||||
fi
|
||||
|
||||
chmod -R 0644 ${SYSTEMD_SERVICE_DIR}/iredadmin.service
|
||||
systemctl daemon-reload &>/dev/null
|
||||
else
|
||||
if [ X"${DISTRO}" == X"FREEBSD" ]; then
|
||||
cp ${NEW_IRA_ROOT_DIR}/rc_scripts/iredadmin.freebsd /usr/local/etc/rc.d/iredadmin
|
||||
perl -pi -e 's#/opt/www#$ENV{HTTPD_SERVERROOT}#g' /usr/local/etc/rc.d/iredadmin
|
||||
|
||||
# Remove 'uwsgi_iredadmin_flags=' in /etc/rc.conf.local
|
||||
if [ -f /etc/rc.conf.local ]; then
|
||||
perl -pi -e 's#^uwsgi_iredadminflags=.*##g' /etc/rc.conf.local
|
||||
fi
|
||||
elif [ X"${DISTRO}" == X'OPENBSD' ]; then
|
||||
cp ${NEW_IRA_ROOT_DIR}/rc_scripts/iredadmin.openbsd ${DIR_RC_SCRIPTS}/iredadmin
|
||||
perl -pi -e 's#/opt/www#$ENV{HTTPD_SERVERROOT}#g' /etc/rc.d/iredadmin
|
||||
|
||||
cp -f ${NEW_IRA_ROOT_DIR}/rc_scripts/iredadmin.openbsd /etc/rc.d/iredadmin
|
||||
chmod 0755 /etc/rc.d/iredadmin
|
||||
|
||||
# Remove 'uwsgi_flags=' in /etc/rc.conf.local
|
||||
if [ -f /etc/rc.conf.local ]; then
|
||||
perl -pi -e 's#^uwsgi_flags=.*iredadmin.*##g' /etc/rc.conf.local
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set owner and permission.
|
||||
chown -R ${IRA_HTTPD_USER}:${IRA_HTTPD_GROUP} ${NEW_IRA_ROOT_DIR}
|
||||
chmod -R 0555 ${NEW_IRA_ROOT_DIR}
|
||||
chmod 0400 ${NEW_IRA_ROOT_DIR}/settings.py
|
||||
|
||||
echo "* Removing old symbol link ${IRA_ROOT_DIR}"
|
||||
rm -f ${IRA_ROOT_DIR}
|
||||
|
||||
echo "* Creating symbol link ${IRA_ROOT_DIR} to ${NEW_IRA_ROOT_DIR}"
|
||||
cd ${HTTPD_SERVERROOT}
|
||||
ln -s ${name_new_version} iredadmin
|
||||
|
||||
# Add missing setting parameters.
|
||||
if grep 'amavisd_enable_logging.*True.*' ${IRA_CONF_PY} &>/dev/null; then
|
||||
add_missing_parameter 'amavisd_enable_policy_lookup' True 'Enable per-recipient spam policy, white/blacklist.'
|
||||
else
|
||||
add_missing_parameter 'amavisd_enable_policy_lookup' False 'Enable per-recipient spam policy, white/blacklist.'
|
||||
fi
|
||||
|
||||
if ! grep '^iredapd_' ${IRA_CONF_PY} &>/dev/null; then
|
||||
add_missing_parameter 'iredapd_enabled' True 'Enable iRedAPD integration.'
|
||||
|
||||
# Get iredapd db password from /opt/iredapd/settings.py.
|
||||
if [ -f /opt/iredapd/settings.py ]; then
|
||||
grep '^iredapd_db_' /opt/iredapd/settings.py >> ${IRA_CONF_PY}
|
||||
perl -pi -e 's#iredapd_db_server#iredapd_db_host#g' ${IRA_CONF_PY}
|
||||
else
|
||||
# Check backend.
|
||||
if egrep '^backend.*pgsql' ${IRA_CONF_PY} &>/dev/null; then
|
||||
export IREDAPD_DB_PORT='5432'
|
||||
else
|
||||
export IREDAPD_DB_PORT='3306'
|
||||
fi
|
||||
|
||||
add_missing_parameter 'iredapd_db_host' '127.0.0.1'
|
||||
add_missing_parameter 'iredapd_db_port' ${IREDAPD_DB_PORT}
|
||||
add_missing_parameter 'iredapd_db_name' 'iredapd'
|
||||
add_missing_parameter 'iredapd_db_user' 'iredapd'
|
||||
add_missing_parameter 'iredapd_db_password' 'password'
|
||||
fi
|
||||
fi
|
||||
perl -pi -e 's#iredapd_db_server#iredapd_db_host#g' ${IRA_CONF_PY}
|
||||
|
||||
if ! grep '^fail2ban_' ${IRA_CONF_PY} &>/dev/null; then
|
||||
# Try to get password of SQL user `fail2ban`.
|
||||
if egrep '^backend.*(mysql|ldap)' ${IRA_CONF_PY} &>/dev/null; then
|
||||
_my_cnf='/root/.my.cnf-fail2ban'
|
||||
if [ -f ${_my_cnf} ]; then
|
||||
_host="$(grep '^host=' ${_my_cnf} | awk -F'host=' '{print $2}' | strip_quotes)"
|
||||
_port="$(grep '^port=' ${_my_cnf} | awk -F'port=' '{print $2}' | strip_quotes)"
|
||||
_user="$(grep '^user=' ${_my_cnf} | awk -F'user=' '{print $2}' | strip_quotes)"
|
||||
_password="$(grep '^password=' ${_my_cnf} | awk -F'password=' '{print $2}' | strip_quotes)"
|
||||
|
||||
[ X"${_host}" == X'' ] && _host='127.0.0.1'
|
||||
[ X"${_port}" == X'' ] && _port='3306'
|
||||
fi
|
||||
elif egrep '^backend.*pgsql' ${IRA_CONF_PY} &>/dev/null; then
|
||||
# Absolute path to ~/.pgpass
|
||||
# - RHEL: /var/lib/pgsql/.pgpass
|
||||
# - Debian/Ubuntu: /var/lib/postgresql/.pgpass
|
||||
# - FreeBSD: /var/db/postgres/.pgpass
|
||||
# - OpenBSD: /var/postgresql/.pgpass
|
||||
for dir in \
|
||||
/var/lib/pgsql \
|
||||
/var/lib/postgresql \
|
||||
/var/db/postgres \
|
||||
/var/postgresql; do
|
||||
_pgpass="${dir}/.pgpass"
|
||||
if [ -f ${_pgpass} ]; then
|
||||
if grep ':fail2ban:' ${_pgpass} &>/dev/null; then
|
||||
_host="127.0.0.1"
|
||||
_port="5432"
|
||||
_user="fail2ban"
|
||||
_password="$(grep ':fail2ban:' ${_pgpass} | awk -F':' '{print $NF}')"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ X"${_host}" != X'' ] && \
|
||||
[ X"${_port}" != X'' ] && \
|
||||
[ X"${_user}" != X'' ] && \
|
||||
[ X"${_password}" != X'' ]; then
|
||||
echo "* Enable Fail2ban SQL integration."
|
||||
add_missing_parameter 'fail2ban_enabled' 'True'
|
||||
add_missing_parameter 'fail2ban_db_host' "${_host}"
|
||||
add_missing_parameter 'fail2ban_db_port' "${_port}"
|
||||
add_missing_parameter 'fail2ban_db_name' "fail2ban"
|
||||
add_missing_parameter 'fail2ban_db_user' "${_user}"
|
||||
add_missing_parameter 'fail2ban_db_password' "${_password}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# FreeBSD uses /var/run/log for syslog.
|
||||
if [ X"${DISTRO}" == X'FREEBSD' ]; then
|
||||
add_missing_parameter 'SYSLOG_SERVER' '/var/run/log'
|
||||
fi
|
||||
|
||||
#
|
||||
# Enable mlmmj integration
|
||||
#
|
||||
if [ -e /opt/mlmmjadmin ]; then
|
||||
echo "* Enable mlmmj integration."
|
||||
# Force to use backend `bk_none`.
|
||||
perl -pi -e 's#^(backend_api).*#${1} = "bk_none"#g' /opt/mlmmjadmin/settings.py
|
||||
|
||||
if egrep '^backend.*(ldap)' ${IRA_CONF_PY} &>/dev/null; then
|
||||
perl -pi -e 's#^(backend_cli).*#${1} = "bk_iredmail_ldap"#g' /opt/mlmmjadmin/settings.py
|
||||
else
|
||||
perl -pi -e 's#^(backend_cli).*#${1} = "bk_iredmail_sql"#g' /opt/mlmmjadmin/settings.py
|
||||
fi
|
||||
|
||||
# Add parameter `mlmmjadmin_api_auth_token` if missing
|
||||
if ! grep '^mlmmjadmin_api_auth_token' ${IRA_CONF_PY} >/dev/null; then
|
||||
# Get first api auth token
|
||||
token=$(grep '^api_auth_tokens' /opt/mlmmjadmin/settings.py | awk -F"[=\']" '{print $3}' | tr -d '\n')
|
||||
echo -e "\nmlmmjadmin_api_auth_token = '${token}'" >> ${IRA_CONF_PY}
|
||||
fi
|
||||
|
||||
echo "* Restarting service: mlmmjadmin."
|
||||
restart_service mlmmjadmin
|
||||
fi
|
||||
|
||||
# Change old parameter names to the new ones:
|
||||
#
|
||||
# - ADDITION_USER_SERVICES -> ADDITIONAL_ENABLED_USER_SERVICES
|
||||
# - LDAP_SERVER_NAME -> LDAP_SERVER_PRODUCT_NAME
|
||||
perl -pi -e 's#ADDITION_USER_SERVICES#ADDITIONAL_ENABLED_USER_SERVICES#g' ${IRA_CONF_PY}
|
||||
perl -pi -e 's#LDAP_SERVER_NAME#LDAP_SERVER_PRODUCT_NAME#g' ${IRA_CONF_PY}
|
||||
|
||||
# Remove deprecated setting: ENABLE_SELF_SERVICE, it's now a per-domain setting.
|
||||
perl -pi -e 's#^(ENABLE_SELF_SERVICE.*)##g' ${IRA_CONF_PY}
|
||||
|
||||
|
||||
# Dependent packages.
|
||||
export REQUIRED_PKGS=""
|
||||
export PIP3_MODS=""
|
||||
# Python modules.
|
||||
export PKG_PY_PIP='python3-pip'
|
||||
export PKG_PY_LDAP='python3-ldap'
|
||||
export PKG_PY_MYSQL='python3-pymysql'
|
||||
export PKG_PY_PGSQL='python3-psycopg2'
|
||||
export PKG_PY_JSON='python3-simplejson'
|
||||
export PKG_PY_DNS='python3-dnspython'
|
||||
export PKG_PY_REQUESTS='python3-requests'
|
||||
export PKG_PY_JINJA='python3-jinja2'
|
||||
# Python modules installed with pip3: uwsgi.
|
||||
|
||||
if [ X"${DISTRO}" == X'RHEL' ]; then
|
||||
if [ X"${DISTRO_VERSION}" == X'7' ]; then
|
||||
export PKG_PY_MYSQL='python36-PyMySQL'
|
||||
export PKG_PY_JSON='python36-simplejson'
|
||||
export PKG_PY_JINJA='python36-jinja2'
|
||||
export REQUIRED_PKGS="${REQUIRED_PKGS} uwsgi uwsgi-plugin-python36 uwsgi-plugin-syslog"
|
||||
|
||||
if rpm -q mod_wsgi &>/dev/null; then
|
||||
remove_pkg mod_wsgi
|
||||
export REQUIRED_PKGS="${REQUIRED_PKGS} python3-mod_wsgi"
|
||||
fi
|
||||
|
||||
else
|
||||
if [ ! -x ${CMD_UWSGI} ]; then
|
||||
# gcc is required to install uwsgi.
|
||||
export REQUIRED_PKGS="${REQUIRED_PKGS} python3-devel python3-pip gcc"
|
||||
export PIP3_MODS="${PIP3_MODS} uwsgi"
|
||||
fi
|
||||
fi
|
||||
|
||||
export PKG_PY_DNS='python3-dns'
|
||||
elif [ X"${DISTRO}" == X'DEBIAN' -o X"${DISTRO}" == X'UBUNTU' ]; then
|
||||
export REQUIRED_PKGS="${REQUIRED_PKGS} uwsgi-core uwsgi-plugin-python3"
|
||||
|
||||
if [ X"${DISTRO_VERSION}" == X'9' ]; then
|
||||
export PKG_PY_LDAP='python3-pyldap'
|
||||
else
|
||||
export PKG_PY_LDAP='python3-ldap'
|
||||
fi
|
||||
elif [ X"${DISTRO}" == X'OPENBSD' ]; then
|
||||
export PKG_PY_PIP='py3-pip'
|
||||
export PKG_PY_JSON='py3-simplejson'
|
||||
export PKG_PY_DNS='py3-dnspython'
|
||||
export PKG_PY_REQUESTS='py3-requests'
|
||||
export PKG_PY_JINJA='py3-jinja2'
|
||||
if [ X"${DISTRO_VERSION}" == X'6.6' -o X"${DISTRO_VERSION}" == X'6.7' ]; then
|
||||
export PKG_PY_MYSQL='py3-mysqlclient'
|
||||
else
|
||||
export PKG_PY_MYSQL='py3-pymysql'
|
||||
fi
|
||||
|
||||
if [ ! -x ${CMD_UWSGI} ]; then
|
||||
export PIP3_MODS="${PIP3_MODS} uwsgi"
|
||||
fi
|
||||
elif [ X"${DISTRO}" == X'FREEBSD' ]; then
|
||||
export PKG_PY_PIP='devel/py-pip'
|
||||
export PKG_UWSGI="www/uwsgi"
|
||||
export PKG_PY_JSON='devel/py-simplejson'
|
||||
export PKG_PY_DNS='dns/py-dnspython'
|
||||
export PKG_PY_REQUESTS='www/py-requests'
|
||||
export PKG_PY_JINJA='devel/py-Jinja2'
|
||||
|
||||
if [ ! -x ${CMD_UWSGI} ]; then
|
||||
export REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_UWSGI}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "* Check and install required packages."
|
||||
if egrep '^backend.*ldap' ${IRA_CONF_PY} &>/dev/null; then
|
||||
[ X"$(has_python_module ldap)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_LDAP}"
|
||||
[ X"$(has_python_module pymysql)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_MYSQL}"
|
||||
elif egrep '^backend.*mysql' ${IRA_CONF_PY} &>/dev/null; then
|
||||
[ X"$(has_python_module pymysql)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_MYSQL}"
|
||||
elif egrep '^backend.*pgsql' ${IRA_CONF_PY} &>/dev/null; then
|
||||
[ X"$(has_python_module psycopg2)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_PGSQL}"
|
||||
fi
|
||||
[ X"$(has_python_module pip)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_PIP}"
|
||||
[ X"$(has_python_module simplejson)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_JSON}"
|
||||
[ X"$(has_python_module dns)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_DNS}"
|
||||
[ X"$(has_python_module requests)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_REQUESTS}"
|
||||
if [ X"$(has_python_module web)" == X'NO' ]; then
|
||||
PIP3_MODS="${PIP3_MODS} web.py>=0.61"
|
||||
else # Verify module version.
|
||||
_webpy_ver=$(${CMD_PYTHON3} -c "import web; print(web.__version__)")
|
||||
if echo ${_webpy_ver} | grep '^0\.[45]' &>/dev/null; then
|
||||
PIP3_MODS="${PIP3_MODS} web.py>=0.61"
|
||||
fi
|
||||
fi
|
||||
[ X"$(has_python_module jinja2)" == X'NO' ] && REQUIRED_PKGS="${REQUIRED_PKGS} ${PKG_PY_JINJA}"
|
||||
|
||||
if [ X"${REQUIRED_PKGS}" != X'' ]; then
|
||||
install_pkg ${REQUIRED_PKGS}
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "Package installation failed, please check console output and fix it manually."
|
||||
exist 255
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ X"${PIP3_MODS}" != X'' ]; then
|
||||
${CMD_PIP3} install -U ${PIP3_MODS}
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "Package installation failed, please check console output and fix it manually."
|
||||
exist 255
|
||||
fi
|
||||
fi
|
||||
|
||||
#------------------------------------------
|
||||
# Add new SQL tables, drop deprecated ones.
|
||||
#
|
||||
export ira_db_host="$(get_iredadmin_setting 'iredadmin_db_host')"
|
||||
export ira_db_port="$(get_iredadmin_setting 'iredadmin_db_port')"
|
||||
export ira_db_name="$(get_iredadmin_setting 'iredadmin_db_name')"
|
||||
export ira_db_user="$(get_iredadmin_setting 'iredadmin_db_user')"
|
||||
export ira_db_password="$(get_iredadmin_setting 'iredadmin_db_password')"
|
||||
|
||||
#
|
||||
# Update sql tables
|
||||
#
|
||||
psql_conn="psql -h ${ira_db_host} \
|
||||
-p ${ira_db_port} \
|
||||
-U ${ira_db_user} \
|
||||
-d ${ira_db_name}"
|
||||
|
||||
if egrep '^backend.*(mysql|ldap)' ${IRA_CONF_PY} &>/dev/null; then
|
||||
echo "* Check SQL tables, and add missed ones - if there's any"
|
||||
${CMD_MYSQL} ${ira_db_name} -e "SOURCE ${IRA_ROOT_DIR}/SQL/iredadmin.mysql"
|
||||
${CMD_MYSQL} ${ira_db_name} -e "ALTER TABLE log MODIFY COLUMN msg TEXT;"
|
||||
|
||||
# Add column `tracking.id`.
|
||||
${CMD_MYSQL} ${ira_db_name} -e "DESC tracking \G" | grep 'Field: id' &>/dev/null
|
||||
if [ X"$?" != X'0' ]; then
|
||||
${CMD_MYSQL} ${ira_db_name} -e "ALTER TABLE tracking ADD COLUMN id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY;"
|
||||
fi
|
||||
|
||||
# Set column `id` to `PRIMARY KEY`
|
||||
_tables='deleted_mailboxes updatelog log tracking'
|
||||
for _table in ${_tables}; do
|
||||
${CMD_MYSQL} ${ira_db_name} -e "DESC ${_table}" | grep '^id.*PRI.*auto_increment' &>/dev/null
|
||||
|
||||
if [ X"$?" != X'0' ]; then
|
||||
${CMD_MYSQL} ${ira_db_name} -e "ALTER TABLE ${_table} ADD PRIMARY KEY (id)"
|
||||
fi
|
||||
done
|
||||
|
||||
elif egrep '^backend.*pgsql' ${IRA_CONF_PY} &>/dev/null; then
|
||||
export PGPASSWORD="${ira_db_password}"
|
||||
|
||||
# Allow log.msg to store long text.
|
||||
${psql_conn} <<EOF
|
||||
ALTER TABLE log ALTER COLUMN msg TYPE TEXT;
|
||||
EOF
|
||||
|
||||
# SQL table: tracking.
|
||||
${psql_conn} -c '\d' | grep '\<tracking\>' &>/dev/null
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "* [SQL] Add new table: iredadmin.tracking."
|
||||
|
||||
${psql_conn} <<EOF
|
||||
CREATE TABLE tracking (
|
||||
id SERIAL PRIMARY KEY,
|
||||
k VARCHAR(50) NOT NULL,
|
||||
v TEXT,
|
||||
time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_tracking_k ON tracking (k);
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Set column `tracking.id` to `PRIMARY KEY`
|
||||
|
||||
# SQL table: domain_ownership.
|
||||
${psql_conn} -c '\d' | grep '\<domain_ownership\>' &>/dev/null
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "* [SQL] Add new table: iredadmin.domain_ownership."
|
||||
|
||||
${psql_conn} <<EOF
|
||||
CREATE TABLE domain_ownership (
|
||||
id SERIAL PRIMARY KEY,
|
||||
admin VARCHAR(255) NOT NULL DEFAULT '',
|
||||
domain VARCHAR(255) NOT NULL DEFAULT '',
|
||||
alias_domain VARCHAR(255) NOT NULL DEFAULT '',
|
||||
verify_code VARCHAR(100) NOT NULL DEFAULT '',
|
||||
verified INT2 NOT NULL DEFAULT 0,
|
||||
message TEXT,
|
||||
last_verify TIMESTAMP NULL DEFAULT NULL,
|
||||
expire INT DEFAULT 0
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_ownership_1 ON domain_ownership (admin, domain, alias_domain);
|
||||
CREATE INDEX idx_ownership_2 ON domain_ownership (verified);
|
||||
EOF
|
||||
fi
|
||||
|
||||
# SQL table: newsletter_subunsub_confirms.
|
||||
${psql_conn} -c '\d' | grep '\<newsletter_subunsub_confirms\>' &>/dev/null
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "* [SQL] Add new table: iredadmin.newsletter_subunsub_confirms."
|
||||
|
||||
_sql="$(cat ${IRA_ROOT_DIR}/SQL/snippets/newsletter_subunsub_confirms.pgsql)"
|
||||
${psql_conn} <<EOF
|
||||
${_sql}
|
||||
EOF
|
||||
unset _sql
|
||||
fi
|
||||
|
||||
# SQL table: settings.
|
||||
${psql_conn} -c '\d' | grep '\<settings\>' &>/dev/null
|
||||
if [ X"$?" != X'0' ]; then
|
||||
echo "* [SQL] Add new table: iredadmin.settings."
|
||||
|
||||
_sql="$(cat ${IRA_ROOT_DIR}/SQL/snippets/settings.pgsql)"
|
||||
${psql_conn} <<EOF
|
||||
${_sql}
|
||||
EOF
|
||||
unset _sql
|
||||
fi
|
||||
fi
|
||||
|
||||
#------------------------------
|
||||
# Cron job.
|
||||
#
|
||||
[[ -d ${CRON_SPOOL_DIR} ]] || mkdir -p ${CRON_SPOOL_DIR} &>/dev/null
|
||||
if [[ ! -f ${CRON_FILE_ROOT} ]]; then
|
||||
touch ${CRON_FILE_ROOT} &>/dev/null
|
||||
chmod 0600 ${CRON_FILE_ROOT} &>/dev/null
|
||||
fi
|
||||
|
||||
# cron job: clean up database.
|
||||
if ! grep 'iredadmin/tools/cleanup_db.py' ${CRON_FILE_ROOT} &>/dev/null; then
|
||||
cat >> ${CRON_FILE_ROOT} <<EOF
|
||||
# iRedAdmin: Clean up sql database.
|
||||
1 * * * * ${CMD_PYTHON3} ${IRA_ROOT_DIR}/tools/cleanup_db.py &>/dev/null
|
||||
EOF
|
||||
fi
|
||||
|
||||
# cron job: clean up database.
|
||||
if ! grep 'iredadmin/tools/delete_mailboxes.py' ${CRON_FILE_ROOT} &>/dev/null; then
|
||||
cat >> ${CRON_FILE_ROOT} <<EOF
|
||||
# iRedAdmin: Remove mailboxes which are scheduled to be removed.
|
||||
1 3 * * * ${CMD_PYTHON3} ${IRA_ROOT_DIR}/tools/delete_mailboxes.py
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "* Replace py2 by py3 in cron jobs."
|
||||
perl -pi -e 's#(.*) python (.*/iredadmin/tools/.*)#${1} $ENV{CMD_PYTHON3} ${2}#' ${CRON_FILE_ROOT}
|
||||
perl -pi -e 's#(.*) python2 (.*/iredadmin/tools/.*)#${1} $ENV{CMD_PYTHON3} ${2}#' ${CRON_FILE_ROOT}
|
||||
perl -pi -e 's#(.*)/usr/bin/python (.*/iredadmin/tools/.*)#${1}$ENV{CMD_PYTHON3} ${2}#' ${CRON_FILE_ROOT}
|
||||
perl -pi -e 's#(.*)/usr/bin/python2 (.*/iredadmin/tools/.*)#${1}$ENV{CMD_PYTHON3} ${2}#' ${CRON_FILE_ROOT}
|
||||
perl -pi -e 's#(.*)/usr/local/bin/python (.*/iredadmin/tools/.*)#${1}$ENV{CMD_PYTHON3} ${2}#' ${CRON_FILE_ROOT}
|
||||
perl -pi -e 's#(.*)/usr/local/bin/python2 (.*/iredadmin/tools/.*)#${1}$ENV{CMD_PYTHON3} ${2}#' ${CRON_FILE_ROOT}
|
||||
|
||||
echo "* Clean up."
|
||||
cd ${NEW_IRA_ROOT_DIR}/
|
||||
rm -f settings.pyc settings.pyo tools/settings.py
|
||||
if [[ -f ${NEW_IRA_ROOT_DIR}/libs/form_utils.py ]]; then
|
||||
# Not a trial license.
|
||||
cd ${NEW_IRA_ROOT_DIR}
|
||||
find . -name '*.so' | xargs rm -f {}
|
||||
cd - &>/dev/null
|
||||
fi
|
||||
|
||||
# Delete all sessions to force admins to re-login.
|
||||
cd ${NEW_IRA_ROOT_DIR}/tools/
|
||||
${CMD_PYTHON3} delete_sessions.py
|
||||
|
||||
echo "* iRedAdmin has been successfully upgraded."
|
||||
restart_web_service
|
||||
|
||||
# Enable and restart service
|
||||
enable_service iredadmin
|
||||
restart_service iredadmin
|
||||
|
||||
echo "* Upgrading completed."
|
||||
|
||||
cat <<EOF
|
||||
<<< NOTE >>> If iRedAdmin doesn't work as expected, please post your issue in
|
||||
<<< NOTE >>> our online support forum: http://www.iredmail.org/forum/
|
||||
EOF
|
||||
Reference in New Issue
Block a user