├── .gitignore ├── 0teach-sa_cron ├── LICENSE ├── README.md ├── create_folders.sh ├── cron_sa_learn.sh ├── email_create_post.sh.sample └── settings.cnf.default /.gitignore: -------------------------------------------------------------------------------- 1 | *.log* 2 | *_log* 3 | *.cnf 4 | *_cnf 5 | settings.cnf 6 | -------------------------------------------------------------------------------- /0teach-sa_cron: -------------------------------------------------------------------------------- 1 | ####################################################################################### 2 | ## 3 | ## Written by Alex S Grebenschikov (zEitEr) 4 | ## www: http://www.poralix.com/ 5 | ## Report bugs and issues: https://github.com/poralix/directadmin-teach-sa/issues 6 | ## Version: 0.10 (beta), Mon Nov 22 00:37:20 +07 2021 7 | ## 8 | ####################################################################################### 9 | ## 10 | ## The script goes through all Directadmin users and teach SpamAssassin 11 | ## per user bases. Every user has its own bayes data, stored at his/her 12 | ## own homedir. 13 | ## 14 | ####################################################################################### 15 | ## 16 | ## MIT License 17 | ## 18 | ## Copyright (c) 2016-2021 Alex S Grebenschikov (www.poralix.com) 19 | ## 20 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 21 | ## of this software and associated documentation files (the "Software"), to deal 22 | ## in the Software without restriction, including without limitation the rights 23 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | ## copies of the Software, and to permit persons to whom the Software is 25 | ## furnished to do so, subject to the following conditions: 26 | ## 27 | ## The above copyright notice and this permission notice shall be included in all 28 | ## copies or substantial portions of the Software. 29 | ## 30 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | ## SOFTWARE. 37 | ## 38 | ####################################################################################### 39 | 40 | 21 * * * * root /usr/local/directadmin/scripts/custom/directadmin-teach-sa/cron_sa_learn.sh > /usr/local/directadmin/scripts/custom/directadmin-teach-sa/cron_sa_learn.log 2>&1 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2025 Alex S Grebenschikov (www.poralix.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # directadmin-teach-sa 2 | A set of scripts to create folders and teach SpamAssassin/rSpamd per user on Directadmin based servers. 3 | 4 | - When using SpamAssassin: Every single user has it's own bayes files stored under his/her homedir. 5 | - When using rSpamd: All users will use shared fuzzy hashes stored in a local storage of rSpamd 6 | 7 | No real-time training is done, no training process is triggered on moving emails between folders. If you need training to be done real-time and immediately after moving an email into a destination folder, you will need to use the official solution from DirectAdmin docs. 8 | 9 | With this solution, just move emails and wait for a script running with cron to find email and to initiate a training process. 10 | 11 | # Installation 12 | 13 | ``` 14 | cd /usr/local/directadmin/scripts/custom/ 15 | git clone https://github.com/poralix/directadmin-teach-sa.git 16 | cd ./directadmin-teach-sa 17 | cp -p ./0teach-sa_cron /etc/cron.d/ 18 | cp -p settings.cnf.default settings.cnf 19 | ``` 20 | 21 | # Upgrade 22 | 23 | ``` 24 | cd /usr/local/directadmin/scripts/custom/directadmin-teach-sa 25 | git config core.fileMode false 26 | git pull 27 | cp -p ./0teach-sa_cron /etc/cron.d/ 28 | ``` 29 | 30 | Compare settings.cnf.default and settings.cnf for possible changes and updates. 31 | 32 | # First run 33 | 34 | Before running update *settings.cnf* if it's required. For example if you need to change folders' names. 35 | 36 | ``` 37 | cd /usr/local/directadmin/scripts/custom/directadmin-teach-sa 38 | ./create_folders.sh --all 39 | ``` 40 | 41 | # Structure 42 | 43 | **create_folders.sh** - a script to create *teach-isspam* and *teach-isnotspam* folders: 44 | 45 | - for a single email-box 46 | - for all email boxes on a single domain 47 | - for all domains on a server 48 | 49 | *Usage*: 50 | 51 | ``` 52 | ./create_folders.sh [||--all|--settings] 53 | ``` 54 | 55 | Can be used with the Directadmin hook-script: /usr/local/directadmin/scripts/custom/email_create_post.sh 56 | 57 | ``` 58 | #!/bin/sh 59 | SCRIPT="/usr/local/directadmin/scripts/custom/directadmin-teach-sa/create_folders.sh"; 60 | [ -x "${SCRIPT}" ] && ${SCRIPT} "${user}@${domain}" >/dev/null 2>&1; 61 | ``` 62 | 63 | or copy the sample *email_create_post.sh.sample*: 64 | 65 | ``` 66 | cp -p /usr/local/directadmin/scripts/custom/email_create_post.sh /usr/local/directadmin/scripts/custom/email_create_post.sh~bak 67 | cp email_create_post.sh.sample /usr/local/directadmin/scripts/custom/email_create_post.sh 68 | chmod 700 /usr/local/directadmin/scripts/custom/email_create_post.sh 69 | chown diradmin:diradmin /usr/local/directadmin/scripts/custom/email_create_post.sh 70 | ``` 71 | 72 | **cron_sa_learn.sh** - a script to run with *cron*. The script goes through all Directadmin users and 73 | train SpamAssassin or rSpamd (whichever is installed on a server). 74 | 75 | The script requires *root* permissions and *sudo* installed on a server to run. It will use *sudo* to 76 | switch to user for teaching SpamAssassin. When training rSpamd no `sudo` is used, there is no need in this case. 77 | 78 | *Usage*: 79 | 80 | ``` 81 | ./cron_sa_learn.sh > ./cron_sa_learn.log 2>&1 82 | ``` 83 | 84 | or run with cron: 85 | 86 | ``` 87 | 21 * * * * root /usr/local/directadmin/scripts/custom/directadmin-teach-sa/cron_sa_learn.sh > /usr/local/directadmin/scripts/custom/directadmin-teach-sa/cron_sa_learn.log 2>&1 88 | ``` 89 | 90 | 91 | **settings.cnf** - a script with settings: 92 | 93 | ``` 94 | # TEACH SPAM FOLDER 95 | TEACH_SPAM_FOLDER="INBOX.teach-isspam"; 96 | 97 | # TEACH NOT SPAM FOLDER 98 | TEACH_HAM_FOLDER="INBOX.teach-isnotspam"; 99 | 100 | # TO DELETE OR NOT EMAIL AFTER TEACHING SPAM 101 | DELETE_TEACH_SPAM_DATA="0"; 102 | 103 | # TO DELETE OR NOT EMAIL AFTER TEACHING NOT SPAM 104 | DELETE_TEACH_HAM_DATA="0"; 105 | 106 | # TO MARK AS READ OR NOT EMAIL AFTER TEACHING SPAM 107 | # DELETE_TEACH_SPAM_DATA SHOULD BE SET TO 0 108 | MARK_AS_READ_TEACH_SPAM_DATA="1"; 109 | 110 | # TO MARK AS READ OR NOT EMAIL AFTER TEACHING NOT SPAM 111 | # DELETE_TEACH_HAM_DATA SHOULD BE SET TO 0 112 | MARK_AS_READ_TEACH_HAM_DATA="1"; 113 | ``` 114 | 115 | IF IT'S REQUIRED YOU SHOULD UPDATE SETTINGS in settings.cnf 116 | 117 | # History 118 | 119 | **v0.11** 120 | 121 | - Get a homedir from /etc/passwd to support /home2, /home3, etc ... (beta) 122 | 123 | **v0.10** 124 | 125 | - A support for Rspamd added (beta) 126 | 127 | **v0.9:** 128 | 129 | - Script failed to start, if no user `admin` exists on a server - FIXED 130 | 131 | **v0.8:** 132 | 133 | - Fixed "too many arguments" error 134 | 135 | **v0.7:** 136 | 137 | - Debug mode added 138 | 139 | **v0.6:** 140 | 141 | - Fix for Perl 5.24.x applied: https://github.com/poralix/directadmin-teach-sa/issues/3 142 | 143 | **v.0.5:** 144 | 145 | - The setting DELETE_TEACH_DATA is deprecated since 146 | - Added DELETE_TEACH_SPAM_DATA to control cleaning from SPAM folder 147 | - Added DELETE_TEACH_HAM_DATA to control cleaning from HAM folder 148 | - Added MARK_AS_READ_TEACH_SPAM_DATA to mark or not as read emails in SPAM folder 149 | - Added MARK_AS_READ_TEACH_HAM_DATA to mark or not as read emails in HAM folder 150 | 151 | # Thanks to 152 | 153 | - John from Directadmin for basic script on help.directadmin.com 154 | - User _jobhh_ https://github.com/jobhh for posting issues 155 | - Andrew Oakley www.aoakley.com for markallread script 156 | 157 | # License and Copyright 158 | 159 | Copyright (c) 2016-2025 Alex S Grebenschikov (www.poralix.com) 160 | 161 | Permission is hereby granted, free of charge, to any person obtaining a copy 162 | of this software and associated documentation files (the "Software"), to deal 163 | in the Software without restriction, including without limitation the rights 164 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 165 | copies of the Software, and to permit persons to whom the Software is 166 | furnished to do so, subject to the following conditions: 167 | 168 | The above copyright notice and this permission notice shall be included in all 169 | copies or substantial portions of the Software. 170 | 171 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 172 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 173 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 174 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 175 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 176 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 177 | SOFTWARE. 178 | -------------------------------------------------------------------------------- /create_folders.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ####################################################################################### 3 | ## 4 | ## Written by Alex S Grebenschikov (zEitEr) 5 | ## www: http://www.poralix.com/ 6 | ## Report bugs and issues: https://github.com/poralix/directadmin-teach-sa/issues 7 | ## Version: 0.11 (beta), Sat Aug 27 17:15:08 +07 2022 8 | ## 9 | ####################################################################################### 10 | ## 11 | ## The script creates IMAP folders TEACH-SPAM and TEACH-ISNOTSPAM on per 12 | ## mailbox, domain bases or for all existing domains. 13 | ## 14 | ####################################################################################### 15 | ## 16 | ## MIT License 17 | ## 18 | ## Copyright (c) 2016-2022 Alex S Grebenschikov (www.poralix.com) 19 | ## 20 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 21 | ## of this software and associated documentation files (the "Software"), to deal 22 | ## in the Software without restriction, including without limitation the rights 23 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | ## copies of the Software, and to permit persons to whom the Software is 25 | ## furnished to do so, subject to the following conditions: 26 | ## 27 | ## The above copyright notice and this permission notice shall be included in all 28 | ## copies or substantial portions of the Software. 29 | ## 30 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | ## SOFTWARE. 37 | ## 38 | ####################################################################################### 39 | 40 | ####################################################################################### 41 | ## 42 | ## !!!! DO NOT CHANGE ANYTHING BELOW HERE !!!! 43 | ## !!!! UNLESS YOU ARE 100% SURE THAT YOU KNOW WHAT YOU DO !!!! 44 | ## !!!! IF IT'S REQUIRED YOU SHOULD UPDATE SETTINGS in settings.cnf !!!! 45 | ## 46 | ####################################################################################### 47 | 48 | DELETE_TEACH_DATA="0"; # deprecated 49 | DELETE_TEACH_SPAM_DATA="0"; # clean spam data 50 | DELETE_TEACH_HAM_DATA="0"; # clean ham data 51 | MARK_AS_READ_TEACH_SPAM_DATA="0"; # mark as read spam data 52 | MARK_AS_READ_TEACH_HAM_DATA="0"; # mark as read ham data 53 | TEACH_SPAM_FOLDER="INBOX.teach-isspam"; 54 | TEACH_HAM_FOLDER="INBOX.teach-isnotspam"; 55 | 56 | SETTINGS_FILE="`dirname $0`/settings.cnf"; 57 | if [ -f "${SETTINGS_FILE}" ]; then . ${SETTINGS_FILE}; fi; 58 | 59 | function e() 60 | { 61 | echo "[$(date)] $@"; 62 | } 63 | 64 | function usage() 65 | { 66 | echo "#############################################################################"; 67 | echo "# #"; 68 | echo "# The script creates IMAP folders TEACH-SPAM and TEACH-ISNOTSPAM on per #"; 69 | echo "# mailbox, domain bases or for all existing domains #"; 70 | echo "# #"; 71 | echo "# Written by Alex S Grebenschikov (zEitEr), web: www.poralix.com #"; 72 | echo "# #"; 73 | echo "#############################################################################"; 74 | echo ""; 75 | echo "Usage $0 [||--all|--settings]"; 76 | echo ""; 77 | echo "Options:" 78 | echo " - specify a mail-box to create folders for a single"; 79 | echo " mailbox, e.g. info@example.com"; 80 | echo " - specify a domain-name to create folder for all "; 81 | echo " existing mail-boxes on the domain, e.g. example.com"; 82 | echo " --all - used to create imap folders for all existing mail boxes"; 83 | echo " on all the existing domains on the server"; 84 | echo " --settings - used to show names of the imap folders for learning"; 85 | echo ""; 86 | exit 1; 87 | } 88 | 89 | function show_options() 90 | { 91 | echo " 92 | # SPAM: 93 | TEACH SPAM FOLDER: ${TEACH_SPAM_FOLDER} 94 | DELETE TEACH SPAM DATA: ${DELETE_TEACH_SPAM_DATA} 95 | MARK AS READ SPAM DATA: ${MARK_AS_READ_TEACH_SPAM_DATA} 96 | 97 | # NOT SPAM: 98 | TEACH NOT SPAM FOLDER: ${TEACH_HAM_FOLDER} 99 | DELETE TEACH NOT SPAM DATA: ${DELETE_TEACH_HAM_DATA} 100 | MARK AS READ NOT SPAM DATA: ${MARK_AS_READ_TEACH_HAM_DATA} 101 | 102 | # IMPORTANT: 103 | The settings can be re-defined in `dirname $0`/settings.cnf 104 | Report bugs and issues here: https://github.com/poralix/directadmin-teach-sa/issues 105 | "; 106 | exit 1; 107 | } 108 | 109 | function process_folder() 110 | { 111 | local loc_folder="${1}"; 112 | local loc_mdir="${2}"; 113 | local loc_dir="${loc_mdir}/.${loc_folder}/"; 114 | 115 | if [ ! -d "${loc_dir}" ]; then 116 | { 117 | e "[OK] [+] Creating directory ${loc_dir}"; 118 | mkdir ${loc_dir}; 119 | 120 | chmod 770 ${loc_dir}; 121 | chown ${user}:mail ${loc_dir}; 122 | [ -d "${loc_dir}" ] && e "[OK] [+] Created ${loc_dir}"; 123 | } 124 | else 125 | { 126 | e "[NOTICE] [-] Directory ${loc_dir} already exists, skipping..."; 127 | } 128 | fi; 129 | 130 | c=$(grep ${loc_folder} ${loc_mdir}/subscriptions -c 2>/dev/null); 131 | if [ "${c}" == "0" ]; then 132 | { 133 | e "[OK] [+] Updating subscriptions"; 134 | echo ${loc_folder} >> ${loc_mdir}/subscriptions 135 | } 136 | else 137 | { 138 | e "[NOTICE] [-] Skipping subscriptions. Already exists..."; 139 | } 140 | fi; 141 | } 142 | 143 | function process_mailbox() 144 | { 145 | email="${1}"; 146 | mailbox=$(echo ${email} | cut -d\@ -f1); 147 | domain=$(echo ${email} | cut -d\@ -f2); 148 | e "[OK] Running for mailbox ${mailbox} on domain ${domain}"; 149 | process_domain "${domain}" "${mailbox}"; 150 | } 151 | 152 | function process_domain() 153 | { 154 | domain="${1}"; 155 | mailbox="${2}"; 156 | user=$(grep ^${domain}: /etc/virtual/domainowners | cut -d\ -f2); 157 | homedir=$(grep -m1 "^${user}:" /etc/passwd | awk -F: '{print $6}'); 158 | 159 | if [ -z "${user}" ]; 160 | then 161 | { 162 | e "[ERROR] Domain ${domain} was not found on the server!"; 163 | return; 164 | } 165 | fi; 166 | 167 | e "[OK] Domain ${domain} owned by user ${user} with homedir ${homedir}"; 168 | 169 | # Create folder for system mail account 170 | if [ -d "${homedir}/Maildir" ]; then 171 | { 172 | if [ -z "${mailbox}" ]; 173 | then 174 | { 175 | e "[OK] Processing system mail account for user ${user} (i.e. ${user}@$(hostname))"; 176 | 177 | # SPAM 178 | if [ -n "${TEACH_SPAM_FOLDER}" ]; then process_folder "${TEACH_SPAM_FOLDER}" "${homedir}/Maildir" >/dev/null; fi; 179 | 180 | # HAM 181 | if [ -n "${TEACH_HAM_FOLDER}" ]; then process_folder "${TEACH_HAM_FOLDER}" "${homedir}/Maildir" >/dev/null; fi; 182 | } 183 | else 184 | { 185 | e "[NOTICE] Skipping system account for user ${user}, as programm is running in a single mailbox mode..."; 186 | } 187 | fi; 188 | } 189 | else 190 | { 191 | e "[NOTICE] Does not seem to have Maildir folder ${homedir}/Maildir/. Skipping..."; 192 | } 193 | fi; 194 | 195 | if [ ! -d "${homedir}/imap/${domain}/" ]; 196 | then 197 | { 198 | e "[NOTICE] Does not seem to have imap folder ${homedir}/imap/${domain}/. Skipping..."; 199 | return; 200 | } 201 | fi; 202 | 203 | e "[OK] Found imap folder ${homedir}/imap/${domain}/"; 204 | 205 | if [ ! -f "/etc/virtual/${domain}/passwd" ]; 206 | then 207 | { 208 | e "[NOTICE] Password file does not exist for the domain ${domain}!"; 209 | return; 210 | } 211 | fi; 212 | 213 | c=$(wc -l "/etc/virtual/${domain}/passwd" | cut -d\ -f1); 214 | 215 | if [ "${c}" == "0" ]; 216 | then 217 | { 218 | e "[NOTICE] Password file found but is empty for the domain ${domain}. Skipping..."; 219 | return; 220 | } 221 | fi; 222 | 223 | e "[OK] Password file found for the domain ${domain}"; 224 | 225 | if [ -z "${mailbox}" ]; 226 | then 227 | { 228 | for box in $(cat /etc/virtual/${domain}/passwd | cut -d\: -f1); 229 | do 230 | { 231 | create_folders; 232 | } 233 | done; 234 | } 235 | else 236 | { 237 | box=$(cat /etc/virtual/${domain}/passwd | grep "^${mailbox}:"|cut -d\: -f1); 238 | create_folders; 239 | } 240 | fi; 241 | } 242 | 243 | function process_all_domains() 244 | { 245 | for domain in $(cat /etc/virtual/domainowners | cut -d\: -f1 | sort | uniq); 246 | do 247 | { 248 | process_domain "${domain}"; 249 | } 250 | done; 251 | } 252 | 253 | function create_folders() 254 | { 255 | local mdir=$(grep ^${box}: /etc/virtual/${domain}/passwd | cut -d\: -f6); 256 | if [ -n "${mdir}" ]; 257 | then 258 | { 259 | mdir="${mdir}/Maildir"; 260 | 261 | if [ -d "${mdir}" ]; 262 | then 263 | { 264 | e "[OK] Found maildir for ${box} in ${mdir}"; 265 | 266 | # SPAM 267 | if [ -n "${TEACH_SPAM_FOLDER}" ]; then process_folder "${TEACH_SPAM_FOLDER}" "${mdir}"; fi; 268 | 269 | # HAM 270 | if [ -n "${TEACH_HAM_FOLDER}" ]; then process_folder "${TEACH_HAM_FOLDER}" "${mdir}"; fi; 271 | } 272 | else 273 | { 274 | e "[WARNING] maildir ${box} was not found in ${mdir}"; 275 | } 276 | fi; 277 | } 278 | fi; 279 | } 280 | 281 | if [ -z "$1" ]; then usage; fi; 282 | 283 | 284 | case $1 in 285 | "--all") 286 | e "[OK] Program started" 287 | process_all_domains; 288 | e "[OK] Program finished" 289 | ;; 290 | "--settings") 291 | show_options; 292 | ;; 293 | *) 294 | e "[OK] Program started" 295 | c=$(echo ${1} | grep -c '@'); 296 | if [ "${c}" == "0" ]; then process_domain "${1}"; 297 | else process_mailbox "${1}"; fi; 298 | e "[OK] Program finished" 299 | ;; 300 | esac; 301 | 302 | 303 | exit 0; 304 | -------------------------------------------------------------------------------- /cron_sa_learn.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ####################################################################################### 3 | ## 4 | ## Written by Alex Grebenschikov (zEitEr) 5 | ## Supported by: Poralix, www.poralix.com 6 | ## Report bugs and issues: https://github.com/poralix/directadmin-teach-sa/issues 7 | ## Version: 0.11 (beta, )Sat Aug 27 17:37:59 +07 2022 8 | ## 0.10 (beta), Mon Nov 22 00:37:20 +07 2021 9 | ## 0.9 (beta), Sat Oct 30 17:37:09 +07 2021 10 | ## 0.8 (beta), Wed Dec 11 23:36:02 +07 2019 11 | ## 0.7 (beta), Sat Nov 18 11:57:31 +07 2017 12 | ## 13 | ####################################################################################### 14 | ## 15 | ## The script goes through all Directadmin users and teaches SpamAssassin/Rspamd 16 | ## per user bases. Every user has its own bayes data, stored uner their own homedir. 17 | ## 18 | ####################################################################################### 19 | ## 20 | ## MIT License 21 | ## 22 | ## Copyright (c) 2016-2022 Alex S Grebenschikov (www.poralix.com) 23 | ## 24 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 25 | ## of this software and associated documentation files (the "Software"), to deal 26 | ## in the Software without restriction, including without limitation the rights 27 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | ## copies of the Software, and to permit persons to whom the Software is 29 | ## furnished to do so, subject to the following conditions: 30 | ## 31 | ## The above copyright notice and this permission notice shall be included in all 32 | ## copies or substantial portions of the Software. 33 | ## 34 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | ## SOFTWARE. 41 | ## 42 | ####################################################################################### 43 | 44 | ####################################################################################### 45 | ## 46 | ## !!!! DO NOT CHANGE ANYTHING BELOW HERE !!!! 47 | ## !!!! UNLESS YOU ARE 100% SURE THAT YOU KNOW WHAT YOU DO !!!! 48 | ## !!!! IF IT'S REQUIRED YOU SHOULD UPDATE SETTINGS in settings.cnf !!!! 49 | ## 50 | ####################################################################################### 51 | 52 | DELETE_TEACH_DATA="0"; # deprecated 53 | DELETE_TEACH_SPAM_DATA="0"; # clean spam data 54 | DELETE_TEACH_HAM_DATA="0"; # clean ham data 55 | MARK_AS_READ_TEACH_SPAM_DATA="0"; # mark as read spam data 56 | MARK_AS_READ_TEACH_HAM_DATA="0"; # mark as read ham data 57 | TEACH_SPAM_FOLDER="INBOX.teach-isspam"; 58 | TEACH_HAM_FOLDER="INBOX.teach-isnotspam"; 59 | 60 | VERSION="0.11 (beta)"; 61 | 62 | SETTINGS_FILE="`dirname $0`/settings.cnf"; 63 | if [ -f "${SETTINGS_FILE}" ]; then . ${SETTINGS_FILE}; fi; 64 | 65 | USER_ID=$(id -u); 66 | 67 | function e() 68 | { 69 | echo "[$(date)] $@"; 70 | } 71 | 72 | function de() 73 | { 74 | [ "${DEBUG}" == "1" ] && echo "[$(date)] $@"; 75 | } 76 | 77 | function check_sudo_exists() 78 | { 79 | SUDO="/usr/local/bin/sudo"; 80 | [ -x "${SUDO}" ] || SUDO="/usr/bin/sudo"; 81 | if [ -x "${SUDO}" ]; 82 | then 83 | { 84 | e "[OK] The binary sudo was found"; 85 | } 86 | else 87 | { 88 | e "[ERROR] The binary sudo was not found! Terminating..."; 89 | exit 1; 90 | } 91 | fi; 92 | } 93 | 94 | function check_sudo_user() 95 | { 96 | local loc_sudo_user; 97 | loc_sudo_user=$(head -1 /usr/local/directadmin/data/admin/admin.list); 98 | c=$(sudo -u "${loc_sudo_user}" whoami 2>&1); 99 | if [ "${c}" != "${loc_sudo_user}" ] && [ -n "${loc_sudo_user}" ]; 100 | then 101 | { 102 | e "[ERROR] Cannot sudo as an user! Terminating..."; 103 | exit 2; 104 | } 105 | fi; 106 | } 107 | 108 | function get_users_list() 109 | { 110 | ls -1 /usr/local/directadmin/data/users/ | sort; 111 | } 112 | 113 | function teach_user_spam() 114 | { 115 | local loc_found="$(find ${1}/{new,cur}/ -type f 2>/dev/null | wc -l)" 116 | local loc_res; 117 | local loc_notlearned; 118 | 119 | if [ "${loc_found}" -ne 0 ]; then 120 | { 121 | e "[OK] [${user}] [+] Found ${loc_found} emails under ${1}, now going to learn spam"; 122 | if [ "${IS_SPAMASSASSIN}" == "1" ]; then 123 | { 124 | loc_res=$(${SUDO} -u ${user} ${TEACH_BIN} --no-sync --spam ${1}/{cur,new}); 125 | loc_notlearned=$(echo "${loc_res}" | grep "from 0 message" -c); 126 | [ "${loc_notlearned}" == "1" ] || DO_SYNC=1; 127 | } 128 | else 129 | { 130 | loc_res=$(${TEACH_BIN} -v --connect ${RSPAMD_SOCK} learn_spam ${1}/{cur,new}); 131 | } 132 | fi; 133 | 134 | e "[OK] [${user}] [+] Teaching user SPAM from ${1}"; 135 | e "[OK] [${user}] [+] ${loc_res}"; 136 | 137 | if [ "${DELETE_TEACH_SPAM_DATA}" == "1" ]; then 138 | { 139 | e "[OK] [${user}] [+] Removing emails from ${1}"; 140 | if [ "${DEBUG}" == "1" ]; then 141 | find ${1}/{new,cur}/ -type f -exec rm -f -v {} \; 142 | else 143 | find ${1}/{new,cur}/ -type f -exec rm -f {} \; 144 | fi; 145 | } 146 | elif [ "${MARK_AS_READ_TEACH_SPAM_DATA}" == "1" ]; then 147 | { 148 | markallread "${1}"; 149 | } 150 | fi; 151 | } 152 | else 153 | { 154 | e "[OK] [${user}] [-] No emails found under ${1}, skipping learning spam"; 155 | } 156 | fi; 157 | } 158 | 159 | function teach_user_ham() 160 | { 161 | local loc_found="$(find ${1}/{new,cur}/ -type f 2>/dev/null | wc -l)" 162 | local loc_res; 163 | local loc_notlearned; 164 | 165 | if [ "${loc_found}" -ne 0 ]; then 166 | { 167 | e "[OK] [${user}] [+] Found ${loc_found} emails under ${1}, now going to learn ham"; 168 | if [ "${IS_SPAMASSASSIN}" == "1" ]; then 169 | { 170 | loc_res=$(${SUDO} -u ${user} ${TEACH_BIN} --no-sync --ham ${1}/{cur,new}); 171 | loc_notlearned=$(echo "${loc_res}" | grep "from 0 message" -c); 172 | [ "${loc_notlearned}" == "1" ] || DO_SYNC=1; 173 | } 174 | else 175 | { 176 | loc_res=$(${TEACH_BIN} -v --connect ${RSPAMD_SOCK} learn_ham ${1}/{cur,new}); 177 | } 178 | fi; 179 | 180 | e "[OK] [${user}] [+] Teaching user HAM from ${1}"; 181 | e "[OK] [${user}] [+] ${loc_res}"; 182 | 183 | if [ "${DELETE_TEACH_HAM_DATA}" == "1" ]; then 184 | { 185 | e "[OK] [${user}] [+] Removing emails from ${1}"; 186 | if [ "${DEBUG}" == "1" ]; then 187 | find ${1}/{new,cur}/ -type f -exec rm -f -v {} \; 188 | else 189 | find ${1}/{new,cur}/ -type f -exec rm -f {} \; 190 | fi; 191 | } 192 | elif [ "${MARK_AS_READ_TEACH_HAM_DATA}" == "1" ]; then 193 | { 194 | markallread "${1}"; 195 | } 196 | fi; 197 | } 198 | else 199 | { 200 | e "[OK] [${user}] [-] No emails found under ${1}, skipping learning ham"; 201 | } 202 | fi; 203 | } 204 | 205 | function markallread() 206 | { 207 | if cd "${1}/new"; 208 | then 209 | de "[OK] [${user}] [DEBUG] Directory changed to ${1}/new"; 210 | e "[OK] [${user}] [+] Going to mark emails as read in ${1}/new/"; 211 | for email in $(ls -1 ./* 2>/dev/null); 212 | do 213 | # Move from /new/ to /cur/ 214 | # Also add status "seen" to message by appending :2,S to filename 215 | de "[OK] [${user}] [DEBUG] Marking email ${email} as read now:"; 216 | mv ${VERBOSE} "${email}" "`echo ${email} | sed -r 's/^.\/(.*)$/..\/cur\/\1:2,S/'`"; 217 | done; 218 | else 219 | e "[ERROR] Failed to change diretory to ${1}/new. Skipping marking emails as read."; 220 | fi; 221 | 222 | if cd "${1}/cur"; 223 | then 224 | de "[OK] [${user}] [DEBUG] Directory changed to ${1}/cur"; 225 | e "[OK] [${user}] [+] Going to mark emails as read in ${1}/cur"; 226 | for email in $(ls -1 ./ -I "*:2,*S*" 2>/dev/null); 227 | do 228 | # Add status "seen" to message by appending S to filename 229 | de "[OK] [${user}] [DEBUG] Marking email ${email} as read now:"; 230 | mv ${VERBOSE} "${email}" "`echo ${email} | sed -r 's/^(.*)$/\1S/'`"; 231 | done; 232 | else 233 | e "[ERROR] Failed to change diretory to ${1}/cur. Skipping marking emails as read."; 234 | fi; 235 | } 236 | 237 | function process_maildir() 238 | { 239 | if [ -n "${TEACH_SPAM_FOLDER}" ]; 240 | then 241 | { 242 | USER_SPAM_FOLDER="${1}/.${TEACH_SPAM_FOLDER}"; 243 | if [ -d "${USER_SPAM_FOLDER}/new" ] || [ -d "${USER_SPAM_FOLDER}/cur" ]; then teach_user_spam "${USER_SPAM_FOLDER}"; fi; 244 | } 245 | fi; 246 | 247 | if [ -n "${TEACH_HAM_FOLDER}" ]; 248 | then 249 | { 250 | USER_HAM_FOLDER="${1}/.${TEACH_HAM_FOLDER}"; 251 | if [ -d "${USER_HAM_FOLDER}/new" ] || [ -d "${USER_HAM_FOLDER}/cur" ]; then teach_user_ham "${USER_HAM_FOLDER}"; fi; 252 | } 253 | fi; 254 | } 255 | 256 | function process_user() 257 | { 258 | USER_HOME=$(grep -m1 "^${user}:" /etc/passwd | awk -F: '{print $6}'); 259 | DO_SYNC=0; 260 | cd "${USER_HOME}" || e "[ERROR] Failed to change directory to ${USER_HOME}"; 261 | 262 | # Processing system mail account for an user 263 | if [ -d "${USER_HOME}/Maildir" ]; then 264 | { 265 | e "[OK] [${user}] [+] found system mail account in ${USER_HOME}/Maildir"; 266 | process_maildir "${USER_HOME}/Maildir"; 267 | } 268 | fi; 269 | 270 | # Processing virtual mail accounts for user's domains 271 | if [ -d "${USER_HOME}/imap" ]; then 272 | { 273 | for domain in $(ls ${USER_HOME}/imap 2>/dev/null); 274 | do 275 | { 276 | DOMAIN_DIR="${USER_HOME}/imap/${domain}"; 277 | if [ -h "${DOMAIN_DIR}" ]; then continue; fi; 278 | 279 | e "[OK] [${user}] [+] found mail-domain ${domain} owned by ${user} in ${DOMAIN_DIR}"; 280 | 281 | for mdir in $(ls -d ${DOMAIN_DIR}/*/Maildir 2>/dev/null); 282 | do 283 | { 284 | e "[OK] [${user}] [+] found ${mdir}"; 285 | process_maildir "${mdir}"; 286 | }; 287 | done; 288 | } 289 | done; 290 | } 291 | fi; 292 | 293 | if [ "${IS_SPAMASSASSIN}" == "1" ]; then 294 | { 295 | # Run once per user, instead of once per email box 296 | if [ "${DO_SYNC}" == "1" ]; then 297 | { 298 | e "[OK] [${user}] Synchronizing the database and the journal"; 299 | # Synchronize the database and the journal if needed 300 | ${SUDO} -u ${user} ${TEACH_BIN} --sync; 301 | ${SUDO} -u ${user} ${TEACH_BIN} --dump magic; 302 | } 303 | else 304 | { 305 | e "[OK] [${user}] [-] Nothing to synchronize yet"; 306 | } 307 | fi; 308 | } 309 | else 310 | { 311 | e "[INFO] No need to sync with Rspamd, yet"; 312 | } 313 | fi; 314 | } 315 | 316 | user=$(whoami); 317 | e "[OK] Started ${VERSION}!"; 318 | e "[INFO] Running $0 as user ${user}"; 319 | e "[INFO] DELETE_TEACH_SPAM_DATA=${DELETE_TEACH_SPAM_DATA}"; 320 | e "[INFO] DELETE_TEACH_HAM_DATA=${DELETE_TEACH_HAM_DATA}"; 321 | e "[INFO] MARK_AS_READ_TEACH_SPAM_DATA=${MARK_AS_READ_TEACH_SPAM_DATA}"; 322 | e "[INFO] MARK_AS_READ_TEACH_HAM_DATA=${MARK_AS_READ_TEACH_HAM_DATA}"; 323 | 324 | # WHAT OPTION DO WE HAVE IN DIRECTADMIN? 325 | SPAMD_OPTION=$(grep ^spamd= /usr/local/directadmin/custombuild/options.conf | cut -d= -f2); 326 | 327 | case "${SPAMD_OPTION}" in 328 | rspamd) 329 | e "[INFO] Rspamd is enabled in DirectAdmin options"; 330 | # IS RSPAMD INSTALLED AND RUNNING? 331 | TEACH_BIN="/usr/local/bin/rspamc"; 332 | [ -f "${TEACH_BIN}" ] || TEACH_BIN="/usr/bin/rspamc"; 333 | [ -f "${TEACH_BIN}" ] || TEACH_BIN="/bin/rspamc"; 334 | if [ ! -f "${TEACH_BIN}" ]; then 335 | { 336 | TEACH_BIN=""; 337 | e "Rspamd is not installed on the server. Terminating..."; 338 | exit 100; 339 | } 340 | fi; 341 | RSPAMD_SOCK="/var/run/rspamd/rspamd_controller.sock"; 342 | IS_RSPAMD=1; 343 | IS_SPAMASSASSIN=0; 344 | ;; 345 | spamassassin) 346 | e "[INFO] SpamAssassin is enabled in DirectAdmin options"; 347 | # IS SPAMASSASSIN INSTALLED AND RUNNING? 348 | TEACH_BIN="/usr/local/bin/sa-learn"; 349 | [ -f "${TEACH_BIN}" ] || TEACH_BIN="/usr/bin/sa-learn"; 350 | [ -f "${TEACH_BIN}" ] || TEACH_BIN="/bin/sa-learn"; 351 | if [ ! -f "${TEACH_BIN}" ]; then 352 | { 353 | TEACH_BIN=""; 354 | e "SpamAssassin is not installed on the server. Terminating..."; 355 | exit 100; 356 | } 357 | fi; 358 | RSPAMD_SOCK=""; 359 | IS_RSPAMD=0; 360 | IS_SPAMASSASSIN=1; 361 | ;; 362 | no|*) 363 | e "[ERROR] AntiSpam is disabled in DirectAdmin options. Terminating..."; 364 | # AntiSPAM not enabled? 365 | TEACH_BIN=""; 366 | RSPAMD_SOCK=""; 367 | exit 1; 368 | ;; 369 | esac; 370 | 371 | check_sudo_exists; 372 | check_sudo_user; 373 | 374 | case "${1}" in 375 | debug|-debug|--debug) 376 | DEBUG=1; 377 | VERBOSE="-v"; 378 | e "[INFO] Enabling DEBUG mode"; 379 | ;; 380 | *) 381 | DEBUG=0; 382 | VERBOSE=""; 383 | ;; 384 | esac; 385 | 386 | if [ "${USER_ID}" == "0" ]; then 387 | { 388 | users=$(get_users_list); 389 | for user in ${users}; 390 | do 391 | { 392 | e "[OK] [${user}] Running for user ${user}"; 393 | process_user; 394 | #${SUDO} -u ${user} id; 395 | e "[OK] [${user}] Finished with user ${user}"; 396 | } 397 | done; 398 | } 399 | else 400 | { 401 | e "[OK] Running for user ${user}"; 402 | process_user; 403 | } 404 | fi; 405 | e "[OK] Finished ${VERSION}!"; 406 | 407 | exit 0; 408 | -------------------------------------------------------------------------------- /email_create_post.sh.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Create IMAP folders for teaching SpamAssassin 3 | SCRIPT="/usr/local/directadmin/scripts/custom/directadmin-teach-sa/create_folders.sh"; 4 | if [ -n "${user}" ] && [ -n "${domain}" ] && [ -x "${SCRIPT}" ]; 5 | then 6 | { 7 | ${SCRIPT} "${user}@${domain}" >/dev/null 2>&1; 8 | } 9 | fi; 10 | -------------------------------------------------------------------------------- /settings.cnf.default: -------------------------------------------------------------------------------- 1 | ####################################################################################### 2 | ## 3 | ## Written by Alex S Grebenschikov (zEitEr) 4 | ## www: http://www.poralix.com/ 5 | ## Report bugs and issues: https://github.com/poralix/directadmin-teach-sa/issues 6 | ## Version: 0.10 (beta), Mon Nov 22 00:37:20 +07 2021 7 | ## 8 | ####################################################################################### 9 | ## 10 | ## MIT License 11 | ## 12 | ## Copyright (c) 2016-2021 Alex S Grebenschikov (www.poralix.com) 13 | ## 14 | ## Permission is hereby granted, free of charge, to any person obtaining a copy 15 | ## of this software and associated documentation files (the "Software"), to deal 16 | ## in the Software without restriction, including without limitation the rights 17 | ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | ## copies of the Software, and to permit persons to whom the Software is 19 | ## furnished to do so, subject to the following conditions: 20 | ## 21 | ## The above copyright notice and this permission notice shall be included in all 22 | ## copies or substantial portions of the Software. 23 | ## 24 | ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | ## SOFTWARE. 31 | ## 32 | ####################################################################################### 33 | 34 | # TEACH SPAM FOLDER 35 | TEACH_SPAM_FOLDER="INBOX.teach-isspam"; 36 | 37 | # TEACH NOT SPAM FOLDER 38 | TEACH_HAM_FOLDER="INBOX.teach-isnotspam"; 39 | 40 | # TO DELETE OR NOT EMAIL AFTER TEACHING SPAM 41 | DELETE_TEACH_SPAM_DATA="0"; 42 | 43 | # TO DELETE OR NOT EMAIL AFTER TEACHING NOT SPAM 44 | DELETE_TEACH_HAM_DATA="0"; 45 | 46 | # TO MARK AS READ OR NOT EMAIL AFTER TEACHING SPAM 47 | # DELETE_TEACH_SPAM_DATA SHOULD BE SET TO 0 48 | MARK_AS_READ_TEACH_SPAM_DATA="0"; 49 | 50 | # TO MARK AS READ OR NOT EMAIL AFTER TEACHING NOT SPAM 51 | # DELETE_TEACH_HAM_DATA SHOULD BE SET TO 0 52 | MARK_AS_READ_TEACH_HAM_DATA="0"; 53 | --------------------------------------------------------------------------------