#!/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 /run/dovecot/login /run/dovecot/token-login /run/dovecot/empty chown -R dovecot:dovecot /run/dovecot chown root:dovenull /run/dovecot/login /run/dovecot/token-login chmod 755 /run/dovecot chmod 750 /run/dovecot/login /run/dovecot/token-login # Remove stale auth token (prevents "compromised token" errors on restart) rm -f /run/dovecot/auth-token-secret.dat # 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 2 chown -R dovecot:dovecot /run/dovecot chown root:dovenull /run/dovecot/login /run/dovecot/token-login 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:5000 virtual_gid_maps = static:5000 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 = 5000 mail_gid = 5000 first_valid_uid = 500 last_valid_uid = 65534 # Auth auth_mechanisms = plain login passdb { driver = passwd-file args = /etc/dovecot/users } userdb { driver = static args = uid=5000 gid=5000 home=/var/mail/%d/%n } # SSL ssl = yes ssl_cert = /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 " 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}:5000:5000::/var/mail/${domain}/${user}::" >> "$rootfs/etc/dovecot/users" # Fix permissions (dovecot needs read access) chmod 644 "$rootfs/etc/dovecot/users" chown root:5000 "$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 [options] Commands: install Install LXC mail server start Start mail server stop Stop mail server restart Restart mail server status Show status add-user 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