This is an updated howto on getting a content filter working with Zimbra 6 Only and above; 5.x versions are not considered in this guide.
Credit should go to CyberNerd who created the original howto.
The plan is to use postfix after queue content filter to run a script that extracts email from the queue, process the email, then injects it back into the queue for delivery. We will be using grep to check the email against a wordlist of regular expressions. We will avoid email loops by using putmail.py to re-inject the email into the mail queue.
This version also only runs for people in a given list (students), which is to be populated via a cronjob. This stops the filter irritating staff users, but is entirely optional - adjust the badwordfilter script to suit your requirements.
* = Can be installed via your package manager
For convenience, copies of some of the files can be downloaded at this URL: http://stuff.m0php.net/zcf/
Nano is the recommended text editor in this guide.
All commands given should be ran as root unless otherwise stated.
In Ubuntu, gain root access from the admin account (set at install) by using this method, and enter your password when prompted:
sudo -s
A directory must be created to spool the mail to whilst it is being processed. Within the Zimbra directory is a good place to put it.
The Zimbra user account needs read and write access to this directory.
mkdir /opt/zimbra/postfix/spool/filter chown zimbra:zimbra /opt/zimbra/postfix/spool/filter chmod 700 /opt/zimbra/postfix/spool/filter
Download putmail.py, then copy it to /usr/local/sbin. Ensure the Zimbra user can access it:
cp putmail.py /usr/local/sbin chown zimbra /usr/local/sbin/putmail.py chmod 500 /usr/local/sbin/putmail.py
On Ubuntu, Putmail consistently complains that the HOME environment variable is not set (error seen in log files and returned dropped messages). The solution is to make the required changes below.
You do not need to do this if you use the copy of putmail.py from here.
About line 48, add the HOME_DIR entry
FROM_HEADER = 'From' TO_HEADER = 'To' CC_HEADER = 'Cc' BCC_HEADER = 'Bcc' HOME_EV = 'HOME' HOME_DIR = '/opt/zimbra/'
About line 139, comment-out the lines to stop the error detection
### Check for HOME present (needed later, checking now saves a lot of work) ### #if not os.environ.has_key(HOME_EV): # Note: I still can't use exit_forcing_print() at this point, the log # filename is not set. #sys.exit(ERROR_HOME_UNSET + "\n") </code python> **Modify line 145 to load the log file from our new HOME_DIR variable** <code python> ### Build the log filename now ### theLogFile = os.path.join(HOME_DIR, CONFIG_DIRECTORY, LOG_FILE)
Modify line 256 to load the config file from our new HOME_DIR variable
configPath = os.path.join(HOME_DIR, CONFIG_DIRECTORY) # temporally
Create the putmail configuration home directory and set owner:
mkdir /opt/zimbra/.putmail chown zimbra:zimbra /opt/zimbra/.putmail
Use a text editor (nano) to create the text file:
nano /opt/zimbra/.putmail/putmailrc
Copy the contents below, changing the admin email address to one of your own. The default port value of 10026 should be OK, but it should match the value set later in master.cf.in.
[config] server = localhost email = admin@example.com port = 10026
Putmail needs to be configured so it knows where its configuration file is. Edit the program using a text editor:
nano /usr/local/sbin/putmail.py
make sure this line points to the configuration home directory:
HOME_DIR = '/opt/zimbra/'
The centrepiece of this whole feature is a list of bad words that will be detected in messages. It is stored in a simple plain-text file, with the following format:
\<badword\> \<badword1\> \<badword1 \<badword2\> \<badword2
Have a look here for some good words to give you a head-start: http://www.noswearing.com/list.php
Name this file (e.g. badwords and place it into a configuration directory (such as /etc/)
nano /etc/badwords
cp badwords /etc/
The script below is what does the actual work of checking if a message contains any of the words listed in the badwords file from above.
This script was modified from the postfix website.
Save it to /usr/local/sbin/badwordfilter.
#!/bin/bash # Simple shell-based filter. It is meant to be invoked as follows: # /path/to/script -f sender recipients... # Localize these. The -G option does nothing before Postfix 2.3. # Changable variables # INSPECT_DIR=/opt/zimbra/postfix/spool/filter SENDMAIL=/usr/local/sbin/putmail.py MAILADMIN=admin@example.com BADWORDS=/etc/badwords # Exit codes from <sysexits.h> # EX_TEMPFAIL=75 EX_UNAVAILABLE=69 # Debug (make sure to create this file and give Zimbra write permissions to it) #echo Bad word filter running for $@ >> /var/log/badwordfilter.log # Clean up when done or when aborting. trap "rm -f in.$$" 0 1 2 3 15 # Start processing. cd $INSPECT_DIR || { echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; } cat >in.$$ || { echo Cannot save mail to file; exit $EX_TEMPFAIL; } # Specify your content filter here. # REMOVE THIS LINE IF NOT USING STUDENT LIST cat in.$$ | grep -Eif /etc/students > /dev/null && { # Debug: #echo Student: $@ >> /var/log/badwordfilter.log cat in.$$ | while read line; do echo $line | grep -v 'Content-Disposition: attachment;' || break; done | grep -if $BADWORDS > /dev/null && { echo " Swearing detected - message has NOT been delivered and your actions HAVE been reported."; $SENDMAIL $MAILADMIN <in.$$; exit $EX_UNAVAILABLE; } # REMOVE THIS LINE IF NOT USING STUDENT LIST } $SENDMAIL "$@" <in.$$ exit $?
chown zimbra:zimbra /usr/local/sbin/badwordfilter chmod 700 /usr/local/sbin/badwordfilter
The student list will contain a list of student accounts that the script checks. If the message being processed doesn't contain anyone in the list, then it skips the filter and gets delivered. Otherwise, the filter runs as expected.
Create a file called /etc/students and give the Zimbra user permission to it:
touch /etc/students chown zimbra:zimbra /etc/students chmod 700 /etc/students
zmprov -l gaa -v | grep -e uid: -e zimbraCOSId | grep -B1 MyZimbraStudentCOSId | grep uid: | awk '{print $2}' > /etc/students
Edit the Zimbra crontab:
su - zimbra crontab -e
Paste the line above into the end of the file, making sure to change the MyZimbraStudentCOSId to the COS ID that your students are assigned to. It should end up looking like this (change the time to suit):
0 6 * * * zmprov -l gaa -v | grep -e uid: -e zimbraCOSId | grep -B1 d9c4bcbb-d02a-46a0-8e08-bbdfcd5f26d1 | grep uid: | awk '{print $2}' > /etc/students
To find the ID of a COS, run this command as Zimbra (substituting your own name):
zmprov gc studentcos|grep zimbraId:
The Postfix part of Zimbra needs to be modified to run the content filter during mail delivery. The file is /opt/zimbra/postfix/conf/master.cf.in.
Backup the original master.cf.in file then edit it.
cd /opt/zimbra/postfix/conf cp master.cf.in master.cf.in.backup nano master.cf.in
Add this section to the bit after maildrop - around line 59:
filter unix - n n - - pipe
flags=Rq user=zimbra argv=/usr/local/sbin/badwordfilter -f ${sender} -- ${recipient}
Navigate to this line below (hit Ctrl + W, enter 10025, then hit Enter to search) and add the content filter named filter to the option -o content_filter=. Not easy to explain, but it should look like this:
127.0.0.1:10025 inet n - n - - smtpd
-o content_filter=filter:dummy
Then add the following to the very end of the file:
127.0.0.1:10026 inet n - n - - smtpd
-o content_filter=
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
-o local_recipient_maps=
-o virtual_mailbox_maps=
-o virtual_alias_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks_style=host
-o mynetworks=127.0.0.0/8
-o strict_rfc821_envelopes=yes
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_address_mappings
This step must be completed as the Zimbra user. It's usually OK to just restart the MTA.
# su - zimbra $ zmcontrol stop $ zmcontrol start
# su - zimbra
$ zmmtactl restart
After completing a Zimbra upgrade, the master.cf.in file is overwritten. It must be replaced after an upgrade of the content filter will fail. It may also be necessary to re-create the postfix spool directory.