CasperSecurity
#!/usr/bin/env bash
# Author: Zhang Huangbin (zhb _at_ iredmail.org)
#---------------------------------------------------------------------
# This file is part of iRedMail, which is an open source mail server
# solution for Red Hat(R) Enterprise Linux, CentOS, Debian and Ubuntu.
#
# iRedMail is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# iRedMail is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with iRedMail. If not, see <http://www.gnu.org/licenses/>.
#---------------------------------------------------------------------
ECHO_INFO()
{
if [ X"$1" == X"-n" ]; then
shift 1
echo -ne "${_INFO_FLAG} $@"
else
echo -e "${_INFO_FLAG} $@"
fi
echo -e "${_INFO_FLAG} $@" >> ${INSTALL_LOG}
}
ECHO_SKIP()
{
echo -e "${_SKIP_FLAG} $@"
echo -e "${_SKIP_FLAG} $@" >> ${INSTALL_LOG}
}
ECHO_QUESTION()
{
if [ X"$1" == X"-n" ]; then
shift 1
echo -ne "${_QUESTION_FLAG} $@"
else
echo -e "${_QUESTION_FLAG} $@"
fi
}
ECHO_ERROR()
{
echo -e "${_ERROR_FLAG} $@"
echo -e "${_ERROR_FLAG} $@" >> ${INSTALL_LOG}
}
ECHO_DEBUG()
{
echo -e "${_DEBUG_FLAG} $@" >> ${INSTALL_LOG}
}
read_setting()
{
answer="${1}"
if [ ! -z "${answer}" ]; then
ANSWER="${answer}"
echo ${ANSWER}
else
read ANSWER
fi
}
backup_file()
{
# Usage: backup_file file1 [file2 file3 ... fileN]
if [ X"$#" != X"0" ]; then
for f in $@; do
if [ -f ${f} ]; then
if [ X"${IREDMAIL_DEBUG}" == X'YES' ]; then
echo -e "${_BACKUP_FLAG} ${f} -> ${f}.${DATE}."
fi
cp -f ${f} ${f}.${DATE}
fi
done
fi
}
check_user()
{
# Check special user privilege to execute this script.
if [ X"$(id -u)" != X"$(id -u ${1})" ]; then
ECHO_ERROR "Please run this script as user: ${1}."
exit 255
else
if [ X"$(id -u)" == X"0" ]; then
export PATH="/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"
else
:
fi
fi
}
check_hostname()
{
echo ${HOSTNAME} | grep '.\..*' &>/dev/null
[ X"$?" != X'0' ] && \
ECHO_ERROR "Please configure a fully qualified domain name (FQDN) in /etc/hosts before we go further.\n\nExample:\n\n127.0.0.1 mail.iredmail.org mail localhost\n" && \
exit 255
}
check_openbsd_install_url()
{
touch /etc/installurl
# Make sure it contains a http/ftp/scp URL
if ! grep -Ei '^(ftp|http|https|scp)://' /etc/installurl &>/dev/null; then
ECHO_INFO "No valid OpenBSD mirror server URL found in file /etc/installurl."
ECHO_INFO "We will use the fastly CDN for this iRedMail installation:"
ECHO_INFO ''
ECHO_INFO " - https://fastly.cdn.openbsd.org/pub/OpenBSD"
ECHO_INFO ''
ECHO_INFO "If you prefer a nearest mirror site, please check links below:"
ECHO_INFO ''
ECHO_INFO " - Mirror sites: http://www.openbsd.org/ftp.html"
ECHO_INFO " - installurl(5): http://man.openbsd.org/installurl.5"
ECHO_INFO ''
echo 'https://fastly.cdn.openbsd.org/pub/OpenBSD' >> /etc/installurl
fi
}
check_pkg() {
# Usage: check_pkg <command> <package>
# It means: <package> owns <command>
cmd="$1"
pkg="$2"
for i in $(echo $PATH|sed 's/:/ /g'); do
[ -x $i/${cmd} ] && export HAS_CMD='YES' && break
done
if [ X"${HAS_CMD}" != X'YES' ]; then
export MISSING_PKGS="${MISSING_PKGS} ${pkg}"
fi
unset HAS_CMD
}
install_missing_pkg() {
if [ X"${MISSING_PKGS}" != X"" ]; then
eval ${install_pkg} ${MISSING_PKGS}
if [ X"$?" != X"0" ]; then
ECHO_ERROR "Please install missing package(s) ${MISSING_PKGS} first." && exit 255
fi
fi
}
check_runtime_dir() {
[ -d ${RUNTIME_DIR} ] || mkdir -p ${RUNTIME_DIR}
# Cleanup
rm -f ${RUNTIME_DIR}/.pkg_install_failed &>/dev/null
}
check_required_os_dirs() {
if [ X"${DISTRO}" == X'FREEBSD' ]; then
[ -d ${LOGROTATE_DIR} ] || mkdir -p ${LOGROTATE_DIR}
fi
}
# Check necessery privileges/files/dirs.
check_env()
{
check_runtime_dir
check_required_os_dirs
# Check user privilege.
check_user root
# Check FQDN hostname.
check_hostname
[ X"${DISTRO}" == X'OPENBSD' ] && check_openbsd_install_url
# Check config tool: dialog.
check_pkg ${BIN_DIALOG} ${PKG_DIALOG}
ECHO_INFO -n "Checking configuration file: ${IREDMAIL_CONFIG_FILE} ..."
if [ -f ${IREDMAIL_CONFIG_FILE} ]; then
if grep '^#EOF$' ${IREDMAIL_CONFIG_FILE} >/dev/null; then
echo -e " [FOUND]"
ECHO_QUESTION -n "Use it for mail server setting? [y|N]"
read_setting ${AUTO_USE_EXISTING_CONFIG_FILE}
case ${ANSWER} in
Y|y|[Yy][Ee][Ss] )
ECHO_INFO "Use config file: ${IREDMAIL_CONFIG_FILE} for mail server setting."
. ${IREDMAIL_CONFIG_FILE}
# Check installation status.
# After each component installation was completed, there
# should be a variable in ${STATUS_FILE}, e.g.
#
# export STATUS_PHP_INSTALLATION='DONE'
# export STATUS_PHP_CONFIGURATION='DONE'
#
if [ -f ${STATUS_FILE} ]; then
ECHO_INFO "Import installation process status from file: ${STATUS_FILE}."
. ${STATUS_FILE}
else
echo '' > ${STATUS_FILE}
fi
# Initialize tip file.
if [ ! -f ${TIP_FILE} ]; then
cat > ${TIP_FILE} <<EOF
${CONF_MSG}
EOF
chown ${SYS_USER_ROOT}:${SYS_GROUP_ROOT} ${TIP_FILE}
chmod 0400 ${TIP_FILE}
fi
;;
N|n|* )
ECHO_INFO "Skip configuration file: ${IREDMAIL_CONFIG_FILE}."
backup_file ${IREDMAIL_CONFIG_FILE}
. ${CONFIG_VIA_DIALOG}
;;
esac
else
ECHO_INFO "Found, but not finished. Launching installation wizard."
. ${CONFIG_VIA_DIALOG}
fi
else
ECHO_INFO "NOT FOUND. Launching installation wizard."
. ${CONFIG_VIA_DIALOG}
fi
}
extract_pkg()
{
if [ X"$2" = X"" ]; then
DST='.'
else
DST="$2"
fi
if echo $1 | grep '.tar.gz$' &>/dev/null; then
ECHO_DEBUG "Extracting: $1 -> ${DST}"
tar zxf $1 -C $DST
elif echo $1 | grep '.tgz$' &>/dev/null; then
ECHO_DEBUG "Extracting: $1 -> ${DST}"
tar zxf $1 -C $DST
elif echo $1 | grep '.tar.bz2$' &>/dev/null; then
# Install bzip2 first.
check_pkg ${BIN_BZIP2} ${PKG_BZIP2}
ECHO_DEBUG "Extracting: $1 -> ${DST}"
tar xjf $1 -C $DST
else
ECHO_ERROR "Unknown archive format."
fi
}
check_status_before_run()
{
# If function was successfully executed, this function will write one line
# in $STATUS_FILE:
#
# export status_[function_name]='DONE'
#
function_name="${1}"
function_status_name="status_${function_name}"
function_status_value="$(eval echo \$${function_status_name})"
if [ X"${function_status_value}" == X"DONE" ]; then
ECHO_SKIP "Function: $1."
else
$function_name
fi
}
# Hash maildir string.
hash_maildir()
{
# Usage:
#
# hash_maildir username
# hash_maildir --no-timestamp username
#
# With '--no-timestamp', the maildir path will not contain timestamp string
if [ X"$1" == X'--no-timestamp' ]; then
_timestamp=''
shift 1
else
_timestamp="-${DATE}"
fi
username="$( echo $1 | tr '[A-Z]' '[a-z]' )"
# Different maildir style: hashed, normal.
if [ X"${MAILDIR_STYLE}" == X"hashed" ]; then
str1="$(echo ${username} | cut -c1)"
str2="$(echo ${username} | cut -c2)"
str3="$(echo ${username} | cut -c3)"
if [ X"${username}" == X"${str1}" ]; then
# Username has only one character
str2="${str1}"
str3="${str1}"
elif [ X"${username}" == X"${str1}${str2}" ]; then
str3="${str2}"
else
:
fi
maildir="${str1}/${str2}/${str3}/${username}${_timestamp}/"
else
maildir="${username}${_timestamp}/"
fi
echo ${maildir}
}
# All Linux/BSD distributions: for one service:
# service_control [enable|disable|start|stop|restart] <service>
# Linux: for multiple services:
# service_control [enable|disable|start|stop|restart] <service1> <service2> <service3> ...
# FreeBSD: for one service:
# service_control enable '<service>_enable' 'YES'
service_control()
{
action="$1" # enable, disable, start, stop, restart
shift 1
service="$1" # first service name
services="$@" # all service names
if [ X"${KERNEL_NAME}" == X'LINUX' ]; then
ECHO_DEBUG "Service control: ${action} ${services}."
if [ X"${USE_SYSTEMD}" == X'YES' ]; then
systemctl ${action} ${services} >> ${INSTALL_LOG} 2>&1
else
for srv in ${services}; do
rc_script="${DIR_RC_SCRIPTS}/$srv"
${rc_script} $action >> ${INSTALL_LOG} 2>&1
done
fi
elif [ X"${KERNEL_NAME}" == X'FREEBSD' ]; then
if [ X"${action}" == X'enable' ]; then
if [ X"$#" == X'2' ]; then
${SYSRC} -f ${RC_CONF_LOCAL} ${1}="${2}" >> ${INSTALL_LOG} 2>&1
fi
elif [ X"${action}" == X'disable' ]; then
# Already handled in functions/*.sh.
:
else
${DIR_RC_SCRIPTS}/${service} $action >> ${INSTALL_LOG} 2>&1
fi
elif [ X"${KERNEL_NAME}" == X'OPENBSD' ]; then
for srv in ${services}; do
rcctl ${action} ${srv} >> ${INSTALL_LOG} 2>&1
done
fi
}
generate_password_hash()
{
_scheme="${1}"
_password="${2}"
if [ X"${_scheme}" == X'BCRYPT' ]; then
_scheme='BLF-CRYPT'
fi
doveadm pw -s "${_scheme}" -p "${_password}"
}
# Create SSL certs/private files.
generate_ssl_keys()
{
ECHO_INFO "Generate self-signed SSL cert (${SSL_KEY_SIZE} bits, expire in 10 years)."
# Create necessary directories.
mkdir -p ${SSL_KEY_DIR} ${SSL_CERT_DIR} &>/dev/null
openssl req \
-x509 \
-nodes \
-days 3650 \
-sha256 \
-subj "/C=${TLS_COUNTRY}/ST=${TLS_STATE}/L=${TLS_CITY}/O=${TLS_COMPANY}/OU=${TLS_DEPARTMENT}/CN=${TLS_HOSTNAME}/emailAddress=${TLS_ADMIN}/" \
-newkey rsa:${SSL_KEY_SIZE} \
-out ${SSL_CERT_FILE} \
-keyout ${SSL_KEY_FILE} &>/dev/null
# Set correct file permission.
chmod +r ${SSL_CERT_FILE}
chmod +r ${SSL_KEY_FILE}
# Fix 'The Logjam Attack'. References:
# - https://weakdh.org/
# - https://weakdh.org/sysadmin.html
ECHO_INFO "Generate Diffie Hellman Group with openssl, please wait."
openssl dhparam -out ${SSL_DH512_PARAM_FILE} 512 5>&1 &>/dev/null
openssl dhparam -out ${SSL_DH1024_PARAM_FILE} 2048 5>&1 &>/dev/null
cat >> ${TIP_FILE} <<EOF
SSL cert keys (size: ${SSL_KEY_SIZE}):
- ${SSL_CERT_FILE}
- ${SSL_KEY_FILE}
EOF
echo 'export status_generate_ssl_keys="DONE"' >> ${STATUS_FILE}
}
# Add alias entry in Postfix /etc/postfix/aliases.
add_postfix_alias()
{
# Usage: add_postfix_alias [src_username] [dest_username or email address]
# File ${POSTFIX_FILE_ALIASES} will be created if not exist.
export alias_src="${1}"
export alias_dest="${2}"
if [ ! -f ${POSTFIX_FILE_ALIASES} ]; then
if [ -f /etc/aliases ]; then
cp -f /etc/aliases ${POSTFIX_FILE_ALIASES}
else
# Create an empty file
touch ${POSTFIX_FILE_ALIASES}
fi
fi
# If alias_src exists, comment it out
if grep "^${alias_src}:" ${POSTFIX_FILE_ALIASES} &>/dev/null; then
perl -pi -e 's/^($ENV{alias_src}:.*)/#${1}/' ${POSTFIX_FILE_ALIASES}
fi
# Add new alias
echo "${alias_src}: ${alias_dest}" >> ${POSTFIX_FILE_ALIASES}
postalias hash:${POSTFIX_FILE_ALIASES} &>/dev/null
unset alias_src
unset alias_dest
}
# Install/Remove binary packages on RHEL/CentOS.
install_pkg_rhel()
{
ECHO_INFO "Installing package(s): $@"
${YUM} -y --disablerepo=rpmforge,ius,remi,remi-php74,remi-safe,atrpms install $@
# Leave a mark if package installation failed.
if [ X"$?" != X'0' ]; then
echo '' > ${RUNTIME_DIR}/.pkg_install_failed
fi
}
remove_pkg_rhel()
{
ECHO_INFO "Removing package(s): $@"
${YUM} remove -y $@
if [ X"$?" != X"0" ]; then
ECHO_ERROR "Package removed failed, please check the terminal output."
echo '' > ${RUNTIME_DIR}/.pkg_remove_failed
fi
}
# Install/Remove binary packages on Debian/Ubuntu.
install_pkg_debian()
{
ECHO_INFO "Installing package(s): $@"
${APTGET} install -y $@
# Leave a mark if package installation failed.
if [ X"$?" != X"0" ]; then
echo '' > ${RUNTIME_DIR}/.pkg_install_failed
fi
}
remove_pkg_debian()
{
ECHO_INFO "Removing package(s): $@"
${APTGET} purge -y $@
if [ X"$?" != X"0" ]; then
ECHO_ERROR "Package removed failed, please check the terminal output."
echo '' > ${RUNTIME_DIR}/.pkg_remove_failed
fi
}
install_pkg_openbsd()
{
ECHO_INFO "Installing packages (pkg_add -i -m): $@"
pkg_add -i -m $@
# Leave a mark if package installation failed.
if [ X"$?" != X"0" ]; then
echo '' > ${RUNTIME_DIR}/.pkg_install_failed
fi
}
freebsd_make_conf_add()
{
# USAGE: freebsd_make_conf_add VAR VALUE
[ -f ${FREEBSD_MAKE_CONF} ] || touch ${FREEBSD_MAKE_CONF}
if [ X"$#" == X'2' ]; then
var="${1}"
value="${2}"
final_option="${1}=${2}"
comment_mark="# ${PROG_NAME}-${var}"
if ! grep "^${comment_mark}$" ${FREEBSD_MAKE_CONF} &>/dev/null; then
ECHO_DEBUG "Update ${FREEBSD_MAKE_CONF}: ${final_option}"
echo "${comment_mark}" >> ${FREEBSD_MAKE_CONF}
echo "${final_option}" >> ${FREEBSD_MAKE_CONF}
else
ECHO_DEBUG "Skip adding option in ${FREEBSD_MAKE_CONF}: ${final_option}"
fi
fi
}
freebsd_make_conf_plus_option()
{
# USAGE: freebsd_make_conf_plus_option VAR VALUE
[ -f ${FREEBSD_MAKE_CONF} ] || touch ${FREEBSD_MAKE_CONF}
if [ X"$#" == X'2' ]; then
var="${1}"
value="${2}"
final_option="${1}+=${2}"
comment_mark="# ${PROG_NAME}-${var}-${value}"
if ! grep "^${comment_mark}$" ${FREEBSD_MAKE_CONF} &>/dev/null; then
ECHO_DEBUG "Update ${FREEBSD_MAKE_CONF}: ${final_option}"
echo "${comment_mark}" >> ${FREEBSD_MAKE_CONF}
echo "${final_option}" >> ${FREEBSD_MAKE_CONF}
else
ECHO_DEBUG "Skip adding option in ${FREEBSD_MAKE_CONF}: ${final_option}"
fi
fi
}
ask_confirm()
{
# Usage: ask_confirm 'prompt text'
prompt_text="${1}"
echo -ne "${prompt_text} [y|N]"
read ANSWER
case ${ANSWER} in
Y|y ) : ;;
N|n|* ) echo "Exit." && exit ;;
esac
}
add_sys_user_group()
{
_user="$1"
_group="$2"
_uid="$3"
_gid="$4"
_home="$5"
_shell="$6"
_args_u="-m -g ${_group}"
_args_g=''
if [ X"${_uid}" != X'' ]; then
_args_u="${_args_u} -u ${_uid}"
fi
if [ X"${_gid}" != X'' ]; then
_args_g="${_args_g} -g ${_gid}"
fi
if [ X"${_home}" != X'' ]; then
_args_u="${_args_u} -d ${_home}"
fi
if [ X"${_shell}" == X'' ]; then
_shell="${SHELL_NOLOGIN}"
fi
_args_u="${_args_u} -s ${_shell}"
ECHO_DEBUG "Create system account: ${_user}:${_group}."
if [ X"${DISTRO}" == X'FREEBSD' ]; then
pw groupadd ${_args_g} -n ${_group} >> ${INSTALL_LOG} 2>&1
pw useradd ${_args_u} -n ${_user} >> ${INSTALL_LOG} 2>&1
elif [ X"${DISTRO}" == X'OPENBSD' ]; then
groupadd ${_args_g} ${_user} >> ${INSTALL_LOG} 2>&1
useradd \
-u ${_uid} \
-g ${_group} \
-s ${_shell} \
${_user} >> ${INSTALL_LOG} 2>&1
else
groupadd ${_args_g} ${_group} >> ${INSTALL_LOG} 2>&1
useradd ${_args_u} ${_user} >> ${INSTALL_LOG} 2>&1
fi
}
update_sysctl_param()
{
export _param="$1"
export _value="$2"
if grep "^${_param}\>" ${SYSCTL_CONF} &>/dev/null; then
# param exists, updating it.
perl -pi -e 's#^($ENV{_param})[ =].*#${1}=$ENV{_value}#' ${SYSCTL_CONF}
else
# param doesn't exist. add a new one.
echo "${_param}=${_value}" >> ${SYSCTL_CONF}
fi
unset _param _value
}
write_iredmail_kv()
{
# usage: write_iredmail_kv <param> <value>
# Storage value to file /root/.iredmail/kv/<param>.
param="$1"
value="$2"
[[ -f ${IREDMAIL_KV_DIR} ]] || mkdir -p ${IREDMAIL_KV_DIR}
f="${IREDMAIL_KV_DIR}/${param}"
echo "${value}" > ${f}
chown -R ${SYS_USER_ROOT}:${SYS_GROUP_ROOT} ${IREDMAIL_KV_DIR}
chmod -R 0400 ${IREDMAIL_KV_DIR}
}