secubox-openwrt/package/secubox/secubox-app-mailserver/files/usr/sbin/mailserverctl
CyberMind-FR 19406e128c fix(mailserver): Complete dovecot permission fixes
- Add dovecot run directory permission setup
- Add dovenull to dovecot group (fixes login directory access)
- Update HISTORY.md with changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-16 15:43:46 +01:00

585 lines
14 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
# 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 = hash:/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