Add permission fix for /etc/dovecot/users in startup script. Without this, dovecot auth fails with "Permission denied" when trying to read the passwd-file for LMTP delivery. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
588 lines
15 KiB
Bash
588 lines
15 KiB
Bash
#!/bin/sh
|
|
# SecuBox Mailserver Controller
|
|
# LXC-based Postfix + Dovecot mail server
|
|
|
|
VERSION="2.0.0"
|
|
CONFIG="mailserver"
|
|
CONTAINER="mailserver"
|
|
LXC_PATH="/srv/lxc/mailserver"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[MAILSERVER]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
# ============================================================================
|
|
# Configuration Helpers
|
|
# ============================================================================
|
|
|
|
uci_get() { uci -q get ${CONFIG}.$1; }
|
|
uci_set() { uci -q set ${CONFIG}.$1="$2"; }
|
|
|
|
require_root() {
|
|
[ "$(id -u)" -eq 0 ] || { error "Root required"; exit 1; }
|
|
}
|
|
|
|
defaults() {
|
|
ip_address="$(uci_get main.ip_address)"
|
|
[ -z "$ip_address" ] && ip_address="192.168.255.30"
|
|
domain="$(uci_get main.domain)"
|
|
[ -z "$domain" ] && domain="secubox.in"
|
|
hostname="$(uci_get main.hostname)"
|
|
[ -z "$hostname" ] && hostname="mail.$domain"
|
|
}
|
|
|
|
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
|
|
|
# ============================================================================
|
|
# LXC Helpers
|
|
# ============================================================================
|
|
|
|
lxc_running() {
|
|
lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "State:.*RUNNING"
|
|
}
|
|
|
|
lxc_exists() {
|
|
[ -d "$LXC_PATH/rootfs" ]
|
|
}
|
|
|
|
create_lxc_config() {
|
|
defaults
|
|
cat > "$LXC_PATH/config" << EOF
|
|
lxc.uts.name = mailserver
|
|
lxc.rootfs.path = dir:${LXC_PATH}/rootfs
|
|
lxc.net.0.type = veth
|
|
lxc.net.0.link = br-lan
|
|
lxc.net.0.flags = up
|
|
lxc.net.0.ipv4.address = ${ip_address}/24
|
|
lxc.net.0.ipv4.gateway = 192.168.255.1
|
|
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
|
|
lxc.cap.drop = sys_module mac_admin mac_override sys_time
|
|
lxc.seccomp.profile =
|
|
lxc.tty.max = 0
|
|
lxc.pty.max = 256
|
|
lxc.cgroup2.memory.max = 512000000
|
|
lxc.init.cmd = /opt/start-mail.sh
|
|
EOF
|
|
}
|
|
|
|
create_startup_script() {
|
|
cat > "$LXC_PATH/rootfs/opt/start-mail.sh" << 'EOF'
|
|
#!/bin/sh
|
|
# Mailserver startup script
|
|
|
|
# Ensure dovenull is in dovecot group (fixes login directory access)
|
|
addgroup dovenull dovecot 2>/dev/null || true
|
|
|
|
# Ensure dovecot run directory exists with correct permissions
|
|
# This fixes anvil-auth-penalty socket permission issues
|
|
mkdir -p /run/dovecot
|
|
chown -R dovecot:dovecot /run/dovecot
|
|
chmod 755 /run/dovecot
|
|
|
|
# Ensure dovecot users file is readable (fixes LMTP lookup errors)
|
|
[ -f /etc/dovecot/users ] && chmod 644 /etc/dovecot/users && chown root:dovecot /etc/dovecot/users
|
|
|
|
# Start services
|
|
/usr/sbin/rsyslogd
|
|
sleep 1
|
|
/usr/sbin/postfix start
|
|
/usr/sbin/dovecot
|
|
|
|
# Give dovecot a moment to create sockets, then fix permissions
|
|
sleep 1
|
|
chown -R dovecot:dovecot /run/dovecot
|
|
|
|
echo "Mail services started"
|
|
|
|
# Keep container running
|
|
exec tail -f /var/log/dovecot.log /var/log/messages 2>/dev/null || exec sleep infinity
|
|
EOF
|
|
chmod +x "$LXC_PATH/rootfs/opt/start-mail.sh"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Alpine Bootstrap
|
|
# ============================================================================
|
|
|
|
bootstrap_alpine() {
|
|
require_root
|
|
defaults
|
|
|
|
log "Bootstrapping Alpine Linux rootfs..."
|
|
|
|
ensure_dir "$LXC_PATH"
|
|
|
|
# Download apk-tools-static
|
|
cd "$LXC_PATH"
|
|
if [ ! -f sbin/apk.static ]; then
|
|
log "Downloading apk-tools-static..."
|
|
curl -L -o apk-tools-static.apk \
|
|
"https://dl-cdn.alpinelinux.org/alpine/v3.21/main/aarch64/apk-tools-static-2.14.6-r3.apk"
|
|
tar -xzf apk-tools-static.apk sbin/apk.static
|
|
rm -f apk-tools-static.apk
|
|
fi
|
|
|
|
# Bootstrap rootfs
|
|
log "Installing base system..."
|
|
./sbin/apk.static -X https://dl-cdn.alpinelinux.org/alpine/v3.21/main \
|
|
-U --allow-untrusted --root rootfs --initdb add \
|
|
alpine-base alpine-baselayout busybox musl openrc
|
|
|
|
# Set up repositories
|
|
mkdir -p rootfs/etc/apk
|
|
cat > rootfs/etc/apk/repositories << 'REPOEOF'
|
|
https://dl-cdn.alpinelinux.org/alpine/v3.21/main
|
|
https://dl-cdn.alpinelinux.org/alpine/v3.21/community
|
|
REPOEOF
|
|
|
|
# Set up DNS
|
|
cat > rootfs/etc/resolv.conf << 'DNSEOF'
|
|
nameserver 8.8.8.8
|
|
nameserver 1.1.1.1
|
|
DNSEOF
|
|
|
|
# Set hostname
|
|
echo "mailserver" > rootfs/etc/hostname
|
|
|
|
log "Base system installed"
|
|
}
|
|
|
|
install_mail_packages() {
|
|
require_root
|
|
|
|
if ! lxc_running; then
|
|
log "Starting container for package installation..."
|
|
lxc-start -n "$CONTAINER" -d
|
|
sleep 3
|
|
fi
|
|
|
|
log "Installing mail packages..."
|
|
lxc-attach -n "$CONTAINER" -- apk update
|
|
lxc-attach -n "$CONTAINER" -- apk add --no-cache \
|
|
postfix \
|
|
dovecot \
|
|
dovecot-lmtpd \
|
|
ca-certificates \
|
|
openssl \
|
|
rsyslog
|
|
|
|
log "Packages installed"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Mail Configuration
|
|
# ============================================================================
|
|
|
|
configure_postfix() {
|
|
defaults
|
|
local rootfs="$LXC_PATH/rootfs"
|
|
|
|
log "Configuring Postfix..."
|
|
|
|
cat > "$rootfs/etc/postfix/main.cf" << EOF
|
|
# Basic config
|
|
myhostname = $hostname
|
|
mydomain = $domain
|
|
myorigin = \$mydomain
|
|
mydestination = \$myhostname, localhost.\$mydomain, localhost
|
|
mynetworks = 127.0.0.0/8 [::1]/128 192.168.255.0/24
|
|
|
|
# Virtual mailbox
|
|
virtual_mailbox_domains = $domain
|
|
virtual_mailbox_base = /var/mail
|
|
virtual_mailbox_maps = lmdb:/etc/postfix/vmailbox
|
|
virtual_uid_maps = static:102
|
|
virtual_gid_maps = static:105
|
|
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
|
|
|
# SASL auth via Dovecot
|
|
smtpd_sasl_auth_enable = yes
|
|
smtpd_sasl_type = dovecot
|
|
smtpd_sasl_path = private/auth
|
|
smtpd_sasl_security_options = noanonymous
|
|
smtpd_sasl_local_domain = \$mydomain
|
|
broken_sasl_auth_clients = yes
|
|
|
|
# TLS
|
|
smtpd_tls_cert_file = /etc/ssl/certs/mail.crt
|
|
smtpd_tls_key_file = /etc/ssl/private/mail.key
|
|
smtpd_tls_security_level = may
|
|
smtp_tls_security_level = may
|
|
|
|
# Restrictions
|
|
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
|
|
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks
|
|
|
|
# Limits
|
|
mailbox_size_limit = 0
|
|
message_size_limit = 52428800
|
|
inet_interfaces = all
|
|
inet_protocols = ipv4
|
|
EOF
|
|
|
|
cat > "$rootfs/etc/postfix/master.cf" << 'EOF'
|
|
smtp inet n - n - - smtpd
|
|
submission inet n - n - - smtpd
|
|
-o syslog_name=postfix/submission
|
|
-o smtpd_tls_security_level=encrypt
|
|
-o smtpd_sasl_auth_enable=yes
|
|
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
smtps inet n - n - - smtpd
|
|
-o syslog_name=postfix/smtps
|
|
-o smtpd_tls_wrappermode=yes
|
|
-o smtpd_sasl_auth_enable=yes
|
|
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
pickup unix n - n 60 1 pickup
|
|
cleanup unix n - n - 0 cleanup
|
|
qmgr unix n - n 300 1 qmgr
|
|
tlsmgr unix - - n 1000? 1 tlsmgr
|
|
rewrite unix - - n - - trivial-rewrite
|
|
bounce unix - - n - 0 bounce
|
|
defer unix - - n - 0 bounce
|
|
trace unix - - n - 0 bounce
|
|
verify unix - - n - 1 verify
|
|
flush unix n - n 1000? 0 flush
|
|
proxymap unix - - n - - proxymap
|
|
proxywrite unix - - n - 1 proxymap
|
|
smtp unix - - n - - smtp
|
|
relay unix - - n - - smtp
|
|
showq unix n - n - - showq
|
|
error unix - - n - - error
|
|
retry unix - - n - - error
|
|
discard unix - - n - - discard
|
|
local unix - n n - - local
|
|
virtual unix - n n - - virtual
|
|
lmtp unix - - n - - lmtp
|
|
anvil unix - - n - 1 anvil
|
|
scache unix - - n - 1 scache
|
|
EOF
|
|
|
|
log "Postfix configured"
|
|
}
|
|
|
|
configure_dovecot() {
|
|
defaults
|
|
local rootfs="$LXC_PATH/rootfs"
|
|
|
|
log "Configuring Dovecot..."
|
|
|
|
cat > "$rootfs/etc/dovecot/dovecot.conf" << 'EOF'
|
|
protocols = imap lmtp
|
|
listen = *
|
|
mail_location = maildir:/var/mail/%d/%n
|
|
mail_uid = 102
|
|
mail_gid = 105
|
|
first_valid_uid = 102
|
|
last_valid_uid = 102
|
|
|
|
# Auth
|
|
auth_mechanisms = plain login
|
|
passdb {
|
|
driver = passwd-file
|
|
args = /etc/dovecot/users
|
|
}
|
|
userdb {
|
|
driver = static
|
|
args = uid=102 gid=105 home=/var/mail/%d/%n
|
|
}
|
|
|
|
# SSL
|
|
ssl = yes
|
|
ssl_cert = </etc/ssl/certs/mail.crt
|
|
ssl_key = </etc/ssl/private/mail.key
|
|
|
|
# Services
|
|
service imap-login {
|
|
inet_listener imap {
|
|
port = 143
|
|
}
|
|
inet_listener imaps {
|
|
port = 993
|
|
ssl = yes
|
|
}
|
|
}
|
|
|
|
service lmtp {
|
|
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
|
mode = 0600
|
|
user = postfix
|
|
group = postfix
|
|
}
|
|
}
|
|
|
|
service auth {
|
|
unix_listener /var/spool/postfix/private/auth {
|
|
mode = 0660
|
|
user = postfix
|
|
group = postfix
|
|
}
|
|
}
|
|
|
|
namespace inbox {
|
|
inbox = yes
|
|
separator = /
|
|
}
|
|
|
|
log_path = /var/log/dovecot.log
|
|
info_log_path = /var/log/dovecot-info.log
|
|
EOF
|
|
|
|
log "Dovecot configured"
|
|
}
|
|
|
|
generate_ssl_cert() {
|
|
local rootfs="$LXC_PATH/rootfs"
|
|
defaults
|
|
|
|
log "Generating SSL certificate..."
|
|
|
|
mkdir -p "$rootfs/etc/ssl/private" "$rootfs/etc/ssl/certs"
|
|
|
|
openssl req -x509 -nodes -days 3650 \
|
|
-newkey rsa:2048 \
|
|
-keyout "$rootfs/etc/ssl/private/mail.key" \
|
|
-out "$rootfs/etc/ssl/certs/mail.crt" \
|
|
-subj "/CN=$hostname/O=SecuBox/C=FR" 2>/dev/null
|
|
|
|
chmod 600 "$rootfs/etc/ssl/private/mail.key"
|
|
log "SSL certificate generated"
|
|
}
|
|
|
|
# ============================================================================
|
|
# User Management
|
|
# ============================================================================
|
|
|
|
cmd_add_user() {
|
|
local email="$1"
|
|
local password="$2"
|
|
|
|
if [ -z "$email" ] || [ -z "$password" ]; then
|
|
echo "Usage: mailserverctl add-user <email> <password>"
|
|
return 1
|
|
fi
|
|
|
|
local user=$(echo "$email" | cut -d@ -f1)
|
|
local domain=$(echo "$email" | cut -d@ -f2)
|
|
local rootfs="$LXC_PATH/rootfs"
|
|
|
|
# Create mailbox entry
|
|
echo "$email ${domain}/${user}/" >> "$rootfs/etc/postfix/vmailbox"
|
|
|
|
# Generate password hash and add to users file
|
|
if lxc_running; then
|
|
local pass_hash=$(lxc-attach -n "$CONTAINER" -- doveadm pw -s SHA512-CRYPT -p "$password")
|
|
echo "${email}:${pass_hash}:102:105::/var/mail/${domain}/${user}::" >> "$rootfs/etc/dovecot/users"
|
|
else
|
|
error "Container not running. Start it first."
|
|
return 1
|
|
fi
|
|
|
|
# Create mail directory
|
|
mkdir -p "$rootfs/var/mail/${domain}/${user}"
|
|
lxc-attach -n "$CONTAINER" -- chown -R vmail:vmail "/var/mail/${domain}"
|
|
|
|
# Rebuild postfix maps
|
|
lxc-attach -n "$CONTAINER" -- postmap /etc/postfix/vmailbox
|
|
|
|
log "User $email added"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Service Control
|
|
# ============================================================================
|
|
|
|
cmd_install() {
|
|
require_root
|
|
log "Installing Mailserver LXC..."
|
|
|
|
defaults
|
|
|
|
if lxc_exists; then
|
|
log "Container already exists"
|
|
else
|
|
bootstrap_alpine
|
|
fi
|
|
|
|
create_lxc_config
|
|
create_startup_script
|
|
|
|
# Start for package installation
|
|
lxc-start -n "$CONTAINER" -d
|
|
sleep 3
|
|
|
|
if lxc_running; then
|
|
install_mail_packages
|
|
lxc-stop -n "$CONTAINER"
|
|
fi
|
|
|
|
configure_postfix
|
|
configure_dovecot
|
|
generate_ssl_cert
|
|
|
|
# Create vmail user directories
|
|
local rootfs="$LXC_PATH/rootfs"
|
|
mkdir -p "$rootfs/var/mail"
|
|
mkdir -p "$rootfs/var/spool/postfix/private"
|
|
|
|
# Create minimal interfaces file
|
|
cat > "$rootfs/etc/network/interfaces" << 'EOF'
|
|
auto lo
|
|
iface lo inet loopback
|
|
EOF
|
|
|
|
uci_set main.enabled '1'
|
|
uci commit ${CONFIG}
|
|
|
|
log "Mailserver installed!"
|
|
log "Add users with: mailserverctl add-user user@domain.com password"
|
|
log "Start with: mailserverctl start"
|
|
}
|
|
|
|
cmd_start() {
|
|
require_root
|
|
|
|
if lxc_running; then
|
|
log "Mailserver already running"
|
|
return 0
|
|
fi
|
|
|
|
if ! lxc_exists; then
|
|
error "Mailserver not installed. Run 'mailserverctl install' first"
|
|
return 1
|
|
fi
|
|
|
|
defaults
|
|
create_lxc_config
|
|
|
|
log "Starting Mailserver LXC..."
|
|
lxc-start -n "$CONTAINER" -d
|
|
sleep 5
|
|
|
|
if lxc_running; then
|
|
log "Mailserver started at $ip_address"
|
|
log "IMAP: ${ip_address}:993 (SSL)"
|
|
log "SMTP: ${ip_address}:465 (SSL)"
|
|
else
|
|
error "Failed to start Mailserver"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
cmd_stop() {
|
|
require_root
|
|
|
|
if ! lxc_running; then
|
|
log "Mailserver is not running"
|
|
return 0
|
|
fi
|
|
|
|
log "Stopping Mailserver..."
|
|
lxc-stop -n "$CONTAINER"
|
|
log "Mailserver stopped"
|
|
}
|
|
|
|
cmd_restart() {
|
|
cmd_stop
|
|
sleep 2
|
|
cmd_start
|
|
}
|
|
|
|
cmd_status() {
|
|
defaults
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo " SecuBox Mailserver v$VERSION (LXC)"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
echo "Configuration:"
|
|
echo " Domain: $domain"
|
|
echo " Hostname: $hostname"
|
|
echo " IP Address: $ip_address"
|
|
echo ""
|
|
|
|
echo "Container:"
|
|
if lxc_running; then
|
|
echo -e " Status: ${GREEN}Running${NC}"
|
|
local pid=$(lxc-info -n "$CONTAINER" 2>/dev/null | grep PID | awk '{print $2}')
|
|
echo " PID: $pid"
|
|
|
|
# Test IMAP
|
|
local imap_test=$(echo "a LOGOUT" | openssl s_client -connect ${ip_address}:993 -quiet 2>/dev/null | head -1)
|
|
if echo "$imap_test" | grep -q "OK"; then
|
|
echo -e " IMAP: ${GREEN}OK${NC}"
|
|
else
|
|
echo -e " IMAP: ${YELLOW}Not responding${NC}"
|
|
fi
|
|
elif lxc_exists; then
|
|
echo -e " Status: ${YELLOW}Stopped${NC}"
|
|
else
|
|
echo -e " Status: ${RED}Not installed${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Ports:"
|
|
echo " SMTP: 25, 465 (SSL), 587 (submission)"
|
|
echo " IMAP: 143, 993 (SSL)"
|
|
echo ""
|
|
}
|
|
|
|
cmd_shell() {
|
|
if ! lxc_running; then
|
|
error "Container not running"
|
|
return 1
|
|
fi
|
|
lxc-attach -n "$CONTAINER" -- /bin/sh
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
SecuBox Mailserver Control v$VERSION (LXC)
|
|
|
|
Usage: mailserverctl <command> [options]
|
|
|
|
Commands:
|
|
install Install LXC mail server
|
|
start Start mail server
|
|
stop Stop mail server
|
|
restart Restart mail server
|
|
status Show status
|
|
|
|
add-user <email> <pass> Add mail user
|
|
shell Open shell in container
|
|
|
|
Examples:
|
|
mailserverctl install
|
|
mailserverctl add-user admin@example.com MyPassword123
|
|
mailserverctl start
|
|
|
|
EOF
|
|
}
|
|
|
|
case "${1:-}" in
|
|
install) shift; cmd_install "$@" ;;
|
|
start) shift; cmd_start "$@" ;;
|
|
stop) shift; cmd_stop "$@" ;;
|
|
restart) shift; cmd_restart "$@" ;;
|
|
status) shift; cmd_status "$@" ;;
|
|
add-user) shift; cmd_add_user "$@" ;;
|
|
shell) shift; cmd_shell "$@" ;;
|
|
service-run) cmd_start ;;
|
|
service-stop) cmd_stop ;;
|
|
help|--help|-h|'') show_help ;;
|
|
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
|
|
esac
|
|
|
|
exit 0
|