Filtering Spam with LMAILER
Internal e-mail lists are common in many organizations. Lists like "everybody", "sales.dept" or "students" can help anyone inside the organization efficiently send a message to a selected group. In our organization we frequently use two lists, "everybody" and "blackboard", that were set up as part of LMAILER, the program we use for handling local group e-mail. The first list is inclusive; all are de facto members, and its use is limited to professional activities. The second list allows people to opt out and is used for more personal messages, of the type "six beautiful kittens are looking for a home".
About three weeks after their installation, both lists began to receive all sort of spam--advertisements, special offers, price listings--from external parties. Obviously that was not the original idea for the lists, so I wrote a Perl script for LMAILER (see Listing 1) that filters messages directed to the lists. The script looks for the sender; if the account does not belong to an internal user, the message is politely rejected. If the sender is allowed to use the list, then LMAILER adds a message at the beginning that reminds the reader that the message was sent through one of the internal lists.
The amount of disk space used each time someone sends an attachment to one of these lists can be considerable, calculating that this must be multiplied by the number of people on the list. So I modified LMAILER so that when an attachment is detected, it is stripped off and stored once in a directory accessible through our web server. Instead of attached files, clickable URLs are sent as part of the message pointing to these.
LMAILER also looks for the attached file extension, and if it is unknown or non-existent, the program tries to hazard a guess. If a user sends a file named document-one, then the message would be received with the link document-one.pdf, thus helping the browser identify the correct application needed.
The real lists have names that are known only to the program. In the examples presented here, the real name is the name known to the users but spelled backwards. In the /etc/alias file, the lines containing the list names would be:
#----------------- local usage lists ---------------- everybody: :include:/lists/everybody leader: :include:/lists/leader
are replaced by
#----------------- local usage lists ----------------< everybody: "|/home/root/lmailer"> leader : "|/home/root/lmailer" #-------------------- real names -------------------- ydobyreve: :include:/lists/everybody redael: :include:/lists/leader
Note the pipe character that tells the mail agent to run LMAILER at /home/root. The newaliases command must be run in order to rebuild the aliases database. The program permissions have to be set as 750, and the owner and group have to be root and dæmon, respectively:
-rwxr-x--- 1 root daemon 14386 Feb 22 12:56 lmailer
Also, LMAILER must be declared in the list of sendmail-permitted programs, usually stored in /usr/lib/sm.bin, by adding a soft link to the program:
lrwxrwxrwx 1 root root 30 Jan 9 15:25 lmailer -> /home/root/lmailer
The configurable values are in the first lines of the script. For security reasons it is better to have a compiled version of LMAILER, but it is very easy to play around with the configuration values and restrictions. As usual, all directory and temporary filenames used have the PID number embedded.
The set of configurable variables contain the absolute paths of the commands used by the script, depending on the UNIX flavor used; you may have to insert the correct path for your Perl interpreter as the first line of the script. $dir_attach must also be defined, the directory where the attachments will be stored. It should include the absolute path of the log file, if you choose to log; a list named %allowed, with the domain names of the machines allowed to send; and $http, the URL of the web server and the directory where the attached files can be reached. Note that $dir_attach and $http can be different because they can belong to different machines and be linked by NFS.
A line invoking the correct subroutine has to be included within "controlled" (see Listing 1). This line is part of a conditional statement. If the recipient ($dest_user) is one of the controlled names, adequate checking will be done.
Because each list can have different restrictions and policies on accepting and rejecting messages, a subroutine must be defined for each. Nothing like a configuration file exists here, for security reasons and because the restrictions can vary. Each subroutine has to be able to check if the message is accepted or rejected. If the message passes all the tests, this subroutine must change the values of the variables $send, use true for accepted; $recip, with the real name of the list, like email@example.com, or any cryptic name because it is a real operating e-mail list (see values for "everybody" in Listing 1); and, optionally, $msg, with a text accepting or rejecting the message.
We can see two of these in the filter script: check_everybody allows local machines to send to the everybody list only if the sender is in the everybody list, and check_leader restricts to a single account the use of the leader list.
As you can see, using LMAILER requires some programming skills, but it has the advantage of being very flexible. For example, suppose an organization has all of its departments in the same building but has many sales offices that are spread out. Each one of these sales offices has its own mail server. In this case, the check_everybody subroutine easily could be modified to send a message to different list names in different machines.
Ricardo Ciria is a mechanical engineer with a masters degree in computer science. He has been a programmer since 1971, and has worked in information systems design, network, system and web site administration. He also loves typing code and listening to Mozart.