Table of Contents Previous Next Index

Section 4 List Exits

Section 4 List Exits
Background for non-technical users: An "exit" is a program supplied by the customer to modify the behavior of a product (such as LISTSERV) in ways that the supplier of the product could not anticipate, or could not afford to support via standard commands or options. The product checks for the presence of the "exit" program and calls it on a number of occasions, called "exit points". In some cases, the "exit" program supplies an answer ("return code") to the main program, which adjusts its behavior accordingly. For instance, LISTSERV may ask an exit program "Is it ok to add JOE@XYZ.EDU to the ABC-L list?", and the program will answer yes or no, and possibly send a message to the user explaining why his subscription was accepted or rejected. In other cases, the "exit point" call is purely informative: the exit program gets a chance to do something, such as sending an informational message to a user, but does not return any answer. Because the exit is a computer program, it must be prepared by a technical person and installed by the LISTSERV maintainer.
4.1 What is a List Exit?
List "exits" are available to control the major events associated with list maintenance. While the implementation of list exits is necessarily system dependent, the list exits themselves (i.e. the tasks that they may carry out, as opposed to how such tasks would be carried out on a particular operating system) are system independent.
To prepare a list exit, you must go through the following steps:
1.
Create an appropriate exit program, as explained below. Let's assume that the program's name is XYZ. (Note that exit programs should be named with only alphanumeric characters, i.e., A-Z, 0-9.)
2.
Modify the LIST_EXITS configuration option in the LISTSERV site configuration file (create one if none was present in your configuration). This variable lists the names of all the "known" exits. For security reasons, LISTSERV will not call an exit which is not listed there. To enable XYZ and ABC as valid list exits, you would set LIST_EXITS to "XYZ ABC" (with or without the quotes, depending on your operating system). You must reboot LISTSERV after making this change.
Note: You do not code the extension of the exit program (if any) into the LIST_EXITS configuration variable. For instance if you are running under Windows NT and your exit is called XYZ.CMD, you set LIST_EXITS to "XYZ". Under OpenVMS, if your exit is called ABC.COM, you set LIST_EXITS to "ABC".
3.
Modify the header of all the lists which should call the XYZ exit, and add "Exit= XYZ". This tells LISTSERV to call the XYZ exit at various exit points.
SECURITY NOTE: Once an exit has been listed in the LIST_EXITS option, ANY list owner may activate it for his own lists. In other words, step 2 merely tells LISTSERV that the program is a "bona fide" exit. There is no mechanism to restrict the use of an exit to a particular list, because it is very easy to implement this in the exit itself, by checking that the name of the list is what you expect or allow.
LISTSERV exits receive one or more parameters as input, and may provide a numeric and (in a few cases) supplemental string result as output. Each operating system has its own set of numeric return codes for various kinds of failures, but LISTSERV always uses the same internal return code system for its exits - anything else would quickly become unmanageable. The value 0 always means "success" or "normal/default action". Positive values indicate various non-default actions, depending on the particular exit point. Negative values indicate system errors. Exit programs are responsible for doing their own error reporting, since LISTSERV has no way to know which errors they may or may not run into.
The location, name, programming language and calling conventions of the exit program vary from one operating system to another. Let's examine the basics first:
On OpenVMS™, the program must be called XYZ.COM on LISTSERV's [.MAIN] subdirectory (LISTSERV_LISTS_DIR) and must be written in DCL.
On unix®, the program must be called XYZ and must be located in the 'home/' subdirectory (i.e., $A). To distinguish them from the standard L-Soft-provided scripts and executables, exit programs must always be named in upper case. Thus, the program must be called XYZ and not xyz. It can be written in any supported language and LISTSERV must have execute permission.1
On Windows™, the program must be called XYZ.CMD and must be located in the MAIN subdirectory. It must be written in the Windows batch language.
Naturally, you are free to call a program in another language from your exit program. The programming language restriction only applies to the exit program itself.
Important: Even though the exit program is usually called from LISTSERV's root directory, it should not make any assumption about its current directory. For optimal portability, the program should always use absolute pathnames to access the various files it might need. For instance, list files should be accessed (if at all) as $A/ or A: or %A%\, and so forth.

It is particularly important to note that an intermediary script file (for instance a CMD file under Windows) must normally make use of absolute paths, including an absolute path to the interpreter that will run the program called by your exit. If your CMD file calls a perl file, your CMD file will need to look something like this (depending on the actual locations of the files in question, of course):

c:\bin\perl -w e:\listserv\main\MYEXIT.pl

Similarly, all files referenced in MYEXIT.pl (including, but not limited to, exit.input and exit.output) must be referenced with absolute paths.

Not using absolute paths in your programming is guaranteed to produce unexpected results, if indeed LISTSERV is even able to find your exit when the exit point is called.
The exit may receive one or more string parameters as input. Most operating systems provide a mechanism to pass one parameter to a script or program, with some restrictions. However, LISTSERV may need to pass several parameters, and the restrictions may not be acceptable. Thus, a system independent parameter passing convention had to be designed. This convention is used by all systems except VM, where multiple parameters of arbitrary length and contents may be passed to a REXX program. On VM, the system independent convention is never used, because it is unnecessary and less efficient than the native method. VM exits use the standard PARSE ARG directive to retrieve their parameters.
The system independent convention uses a disk file called 'exit.input' in the same directory as the exit program. This is a standard text file, where each record is one input string parameter. This file is always created if there are 2 or more string parameters for the exit, or if the EXIT_INPUT configuration parameter is set to the value 1. In addition, it is always created on Windows™ operating systems. Under OpenVMS™ and unix®, the file is not created when there is only one parameter and EXIT_INPUT defaults to 0. Since most exits only have a single parameter, this improves performance in most cases. Note that LISTSERV will take care of deleting the 'exit.input' when appropriate.
Regardless of whether or not the 'exit.input' file is created, the first parameter is always passed to the exit using system-specific methods under OpenVMS™ and unix®. Under Windows™ systems, the first parameter is only passed through the 'exit.input' file. Again, this may simplify programming for simple exits.
SECURITY NOTE: LISTSERV will always quote/escape the first parameter to prevent the recognition of special characters by the underlying operating system. However, your program should be very careful in its use of the parameters in any subsequent system call. For instance, if the parameter to your unix® exit is the string "a|b", LISTSERV will quote the vertical bar so that it does not result in the execution of the program 'b', and so that the value "a|b" is present in your argument vector. However, you must still be careful in the use of the arguments within your program, especially if you plan to launch a compiled program from a shell and pass it the same arguments. In that case L-Soft recommends the use of EXIT_INPUT = 1, which allows the second program to read its parameters safely from the 'exit.input' file.
For list exits, there is at least one input parameter, of the form (blank separated):
epname listname more
where 'epname' is the name of the entry point being called (SUB_FILTER, SUB_FAIL, etc.), 'listname' is the name of the list, and 'more' depends on the particular exit point. Under VM, 'more' may contain '15'x characters delimiting additional parameters. Again, while 'epname' and 'listname' are unlikely to contain "tricky" characters, the same cannot be assumed about the remainder of the parameter string.
In most cases, your program will only handle a limited set of exit points. When it does not recognize the 'epname', it should exit with the numeric result 0, which tells LISTSERV to take the default action. To exit with the result 0, you can take a normal operating system dependent exit. In particular, success status codes are translated to 0 under OpenVMS™. To return any other numeric code, or to return a string code, you must use the system independent parameter return convention return below, except on VM where the operating system provides a suitable native convention for the return of one parameter of arbitrary length and contents. So, REXX programs return their results with a standard RETURN or EXIT statement. The first blank-delimited word is interpreted as the numeric result, and the rest, if any, is the string result. In other words, the return string is broken up into number and string in exactly the same manner as with a PARSE VAR RESULT NUMBER STRING instruction. On VM, the system independent return convention is not used, because it is unnecessary and less efficient than the native method.
The system independent convention is based on a file called 'exit.output', in the same directory as the exit program. LISTSERV erases this file before calling your program, and reads it when it regains control. This file is a standard text file, which contains a number of directives. To set the numeric return code, you use the EXIT directive:
EXIT 2
This would set the numeric return code to 2. To set the string result, use:
EXIT-STRING This is the exit string
Note: You must ALWAYS set the numeric code if you want the string result to have any effect. The default numeric code is 0, which means "default behavior", and the default behavior never uses the string you supply. By definition, the default behavior is whatever LISTSERV would do if the exit were not present.
In addition to EXIT and EXIT-STRING, a number of other directives are available. For instance, you can tell LISTSERV to send a message to the originating user, to explain why the exit rejected his subscription request. These directives are processed sequentially until the end of the file. Note that the EXIT directives merely set the final exit codes. They do not interrupt the processing of the 'exit.output' file, which is always read to the end of the file. This means that, if you change your mind about the exit code, you can write a new EXIT instructions and LISTSERV will use the last value that you supplied.
Each directive has 0 or more mandatory parameters, and may support a number of optional parameters, which are always listed after the mandatory ones. Some parameters may be simple blank-delimited keywords or options, while others may contain arbitrary data. The exit should not need to provide placeholders for optional parameters, and above all it should be possible to add new optional parameters without requiring all exits to be rewritten. To solve this problem, each directive was given two forms: a simple form, where the entire directive fits in a single line, and an explicit form, where you indicate the number of parameters that you intend to provide, and each parameter follows on a line by its own. In the simple form, the mandatory parameters are filled from the data supplied on the single directive line, and all the optional parameters are set to their default value. Each blank delimited word supplies one parameter, until the first N-1 parameters have been set. The remainder is used for the last parameter. Here is an example of the simple form:
TELL jack@XYZ.COM The FOO-L list is only open to FOO Inc. employees.
Parameter 1 (mandatory): "jack@XYZ.COM"
Parameter 2 (mandatory): "The FOO-L list is only open to FOO Inc. employees."
Parameter 3 (optional): <default>
If, on the other hand, you want to send the message to more than one person, you need to use the explicit form. In the explicit form, you specify the parameters on separate lines and modify the directive to add the number of lines which you are passing to the processor. By way of example, here is the explicit form of the TELL directive shown above, where the number 2 is added to the directive, telling LISTSERV that there will be two lines of parameters following:
TELL2
jack@XYZ.COM cc: honcho@FOO.COM
The FOO-L list is only open to FOO Inc. employees.
If you wanted the message echoed to the LISTSERV console log, you would have to add another line containing the ECHO parameter, and you would have to specify TELL3:
TELL3
jack@XYZ.COM cc: honcho@FOO.COM
The FOO-L list is only open to FOO Inc. employees.
ECHO
It is always safer to use the explicit form if you are not sure how many words you will have in the various parameters. The simple form is provided mostly for directives such as EXIT or EXIT-STRING which only take one parameter, and for hardcoded parameters.
Currently, the supported directives are as follows. The "VM API" indicates the corresponding REXX API for VM users (it is not possible to provide an API for non-VM systems because the exits run in a different virtual address space and may not call back into LISTSERV entry points).
Name: EXIT, EXIT-CODE, RETURN
P1: Numeric return code
Action: Sets numeric return code; does NOT abort exit.output processing!
VM API: EXIT/RETURN
Name: EXIT-STRING
P1: String result code
Action: Sets exit string result
VM API: EXIT/RETURN
 
Name: TELL, LTELL
P1: List of RFC822 addresses
P2: Message text
P3 (opt): Blank separated list of options (default = off)
- ECHO: echoes message to log
- RAGGED: flows but does not justify message
Action: Sends long (paragraph) message to specified users
VM API: LSVLTELL
 
Name: TELLNF
P1: List of RFC822 addresses
P2: Message text
P3 (opt): Blank separated list of options (default = off)
- ECHO: echoes message to log
Action: Sends unformatted message to specified users
VM API: LSVTELL
4.2 List Exit Points
The following list exit points are available.
4.2.1 ADD/SUBSCRIBE Exit Points
Name: SUB_FILTER
Parameters: One; originator's e-mail address
Return code: 0=Accept, 1=Reject
Description: This exit point gives you a chance to reject a subscription request by returning a nonzero value. This adds to rather than replaces the "Filter=" keyword.
 
Name: SUB_FAIL
Parameters: One; originator's e-mail address
Return code: Ignored/reserved - always return 0
Description: This exit point is called whenever a subscription is rejected (in particular, it is called if you return 1 from SUB_FILTER). You may send additional messages to the user, log the event, and so on.
 
Name: SUB_REQ
Parameters: One; originator's e-mail address
Return code: Ignored/reserved - always return 0
Description: The user's request for subscription is being forwarded to the list owners. You may send additional messages to the user, log the event, and so on. To implement custom subscription/rejection, use the SUB_FILTER exit with "Subscription= Open". When SUB_REQ is called, it is too late to let the user through.
 
Name: SUB_NEW, ADD_NEW
Parameters: One; originator's e-mail address
Return code: Ignored/reserved - always return 0
Description: This exit point notifies you that the user has been successfully subscribed to the list (SUB_NEW is for the SUBSCRIBE command, ADD_NEW corresponds to the ADD command). You can send additional messages, log the event, and so on.
4.2.2 DELETE/SIGNOFF/CHANGE Entry Points
Name: CHG_REQ
VM Parameters: Three; originator's e-mail address '15'x target subscriber's e-mail address '15'x new e-mail address
Non-VM Parameters: Three; originator's e-mail address '20'x target subscriber's e-mail address '20'x new e-mail address
Return code: 0=Accept, 1=Reject
Description: This exit point is called for the CHANGE command when issued by a list subscriber. It is NOT called for CHANGE commands originating from the list owner or node administrator on behalf of a subscriber. This exit point allows you to reject the operation. If you return the value 1, LISTSERV rejects the operation.
 
Name: DEL_FILTER
VM Parameters: One or two; target e-mail address '15'x originator's address
Non-VM Parameters: One or two; target e-mail address '20'x originator's address
Return code: 0=Accept, 1=Reject, 2=Interrupt processing of command
Description: This exit point is called for all SIGNOFF commands, and for DELETE commands issued by a registered Node Administrator. It is NOT called for DELETE commands originating from the list owner. If you return the value 1, LISTSERV does not delete the target e-mail address but continues to look for more addresses matching the pattern provided, and the exit will be called again as appropriate. If you return the value 2, LISTSERV simply interrupts the processing of the SIGNOFF command and terminates the command with no further message (i.e., you have to send your own explanation back to the command originator). If the request is rejected, you should check for the presence of the second parameter and send an explanatory message to that address, or to the target address if only one parameter was specified.
 
Name: DEL_SIGNOFF
Parameters: One; originator's e-mail address
Return code: 0=Continue, 1=Do not notify owner
Description: This exit point is called for the SIGNOFF command only, when a user has been successfully removed from the list. The farewell message, if any, has already been sent. You may issue additional messages, log the event, and so on. A return value of 1 directs LISTSERV not to mail the "SIGNOFF1" form to the list owners.
 
Name: DEL_DELETE
VM Parameters: Two; target e-mail address '15'x originator's address
Non-VM Parameters: Two; target e-mail address '20'x originator's address
Return code: 0=Continue, 1=Do not notify owner
Description: This exit point is called for the DELETE command only, after a user has been removed from the list. You may issue additional messages, log the event, and so on. A return value of 1 directs LISTSERV not to mail the "DELETE1" form to the list owners.
4.2.3 Other Exit Points
Name: SET_REQ
VM Parameters: Three; originator's address '15'x list of options to be altered '15'x target e-mail address
Non-VM Parameters: Three; originator's address '20'x list of options to be altered '20'x target e-mail address
Return code: 0=Accept, 1=Reject, 2=Alter
Description: This exit point is called for all SET commands that do not originate from the list owner. The first parameter (originator's address) is the address you should use to send replies or informational messages to the command originator. The second parameter (list of options to be altered) is a blank-separated list of command options, in their canonical spelling. If topics have been specified, they are listed last, after the word 'TOPICS:', with the spelling provided by the user. Bear in mind that topic change requests are not necessarily a list of the new topics to be enabled, and may contain complex '+' or '-' directives. Finally, the third parameter (target e-mail address) is the address as it appears in the list, and is provided for the sake of completeness; in most cases you will not need to examine it. If you return the value 1, the command is rejected and no option is modified. A return value of 2 indicates that the list of options and/or topics should be altered before the changes are performed. The exit string must contain a replacement for the second input parameter, in the same format. LISTSERV will assume that any options or topics specified in this fashion are syntactically correct; while incorrect values will not cause any problem and will be properly rejected as invalid options, the error message(s) returned to the user may not be as helpful as they ordinarily would.
 
Name: POST_FILTER
Parameters: One; e-mail address of the poster.
Return code: 0=Accept, 1=Reject
Description: This exit point is called for messages posted to a list. It can be used to compare the e-mail address of the poster to a list of users who should (or should not) be allowed to post. It can further be used to scan the body of the e-mail message, i.e., to accept or reject the message based on its content. The exit places the message to be checked in the file $D/listserv.cmsut1 (where $D is the value assigned with .SD D in system.cfg or CORPORAT SYSVARS, D= in go.sys, or D\ in system_config.dat, depending on your OS platform; in any case, by default this is LISTSERV's TMP directory or minidisk).
A return value of 0 indicates that LISTSERV should continue processing the posting, while a return value of 1 indicates that LISTSERV should reject the posting.
Note: Your exit program MAY NOT edit or alter the listserv.cmsut1 file as LISTSERV has already loaded it preparatory to processing it. The exit point ONLY allows you to accept or reject the message.
4.2.4 General Remarks
Important: List exits may not make any change to the LIST file, because the command processor may have changes of its own to make to the file, or may have kept the file open for further processing. Under VM only, If you really have to change the file, use CP MSG * CMS EXEC to schedule a new call to the exit after command processing completes.
The template processor can be called from list exits. The syntax is:
Call LSVTMAIL recipients,template_name,listname,keywords
keywords = 'KWD1 value of first keyword'||'15'x'KWD2 second keyword'||...
Note: The change in calling sequence from LSVIMAIL. LSVIMAIL was removed in version 1.8b and is no longer available.
4.3 Local Command Definition (non-VM)
It is possible to define "local" LISTSERV commands on non-VM systems. A "local" command is a locally developed extension to the LISTSERV command set, which can be installed without making any modification to LISTSERV itself. To install a local command, you must perform the following steps:
1.
Create an exit program to implement the command, as described below. Let's assume the program is called ABC. Command and list exits share the same basic attributes and programming interface, and in particular they are located in the same directory and follow the same naming and calling conventions.
2.
Choose a name for your local command. Names starting with a letter are reserved for L-Soft use; other names are reserved for customer use. You could call your command /ABC for instance.
3.
Modify (or create) the file 'localcmd.file' in the main LISTSERV subdirectory (the same directory where the lists, exits and other LISTSERV files are located). You must register the command in this file to define its existence to LISTSERV and indicate which exit should be called to execute the command. The format is:
/ABC 3 ABC DEF
/ABC is the full name of the command, 3 is the minimum abbreviation (allowing /AB or /ABC), ABC is the name of the exit program to execute, and DEF is an optional parameter to be passed to the exit (this allows multiple similar commands to be served by the same exit). NOTE CAREFULLY that the entries MUST be in UPPER CASE. If they are entered in lower case, LISTSERV will not recognize them.
4.
Optionally, modify (or create) the file 'localcmd.helpfile' in the same directory to provide a brief (1-2 lines) description of your new command. This is a free form file whose contents are appended to the standard HELP message. If the command is important, you may want to mention it there.
You must reboot LISTSERV for the changes to take effect. This is a change from earlier versions.
The ABC program is called as an exit with two parameters. The first one takes the following form:
origin command arguments
where 'origin' is the address of the command originator, 'command' is the name of the command ('/ABC' in the present example), and 'arguments' are the command arguments, if any, provided by the user. The second parameter is the optional DEF parameter from 'localcmd.file'.
Typically, your program will parse the arguments, decide on a course of action, and issue a number of messages to the user. The exit code is immaterial; there is no particular course of action to select for command processing.
4.4 SPAM_EXIT
This “exit point” allows you to use a third-party spam filter to scan messages processed by LISTSERV. Although this hook can in principle be used with any spam scanning product, all the examples and step-by-step instructions in this section will relate to SpamAssassin, a popular freeware spam filter that can be downloaded from http://spamassassin.apache.org.
Note: L-Soft did not author SpamAssassin and is unable to correct problems with the SpamAssassin product itself. L-Soft does not make any legal representations or warranties about SpamAssassin. Although L Soft’s support department will be glad to answer questions about the integration of SpamAssassin and LISTSERV, we cannot answer questions about SpamAssassin itself.
4.4.1 Formal documentation
SPAM_EXIT is a standard LISTSERV exit, with all that this entails. The same OS-specific naming requirements used for regular LISTSERV exit points are enforced for the SPAM_EXIT exit point. However, SPAM_EXIT is defined separately in the site configuration file as it is a server-level exit rather than a list-level exit.
LISTSERV scans messages in the following sequence:
Virus scan -> SPAM_MAXSIZE test -> whitelist/blacklist -> SPAM_EXIT -> future L-Soft supplied tests
The rationale for doing things in this order is that viruses are far more dangerous than spam, so LISTSERV wants to identify them as quickly as possible, and in particular before any whitelist rule has had the opportunity to accept them. Besides, virus scans are much faster than spam scans.
The exit is formally defined as follows:
 
Name: SPAM_EXIT
Parameters: One; SCAN [listname] | REPORT
Return code: 0=Accept, 1=Local whitelist, 2=Reject
 
Its primary input is the file spam.tmp in the D directory (typically, unix, ~listserv/tmp ; Windows, \LISTSERV\TMP ; OpenVMS, LISTSERV_ROOT:[TMP]). This contains a copy of the whole message, header and body.
When using the SCAN parameter, the exit returns:
0: continue normally, per the LISTSERV exit standard. Currently, this means the message is always accepted in practice, but future L-Soft supplied tests would run.
2: reject the message. The exit string must then contain an error message to be reported to the sender. LISTSERV will use a standard message if no exit string is supplied, but this standard message is vague since LISTSERV does not know what the exit does.
When using this exit, it is very important to test things carefully, since a mistake could mean that every message is rejected. If for instance the script is not found by the operating system due to a misspelling, and the operating system happens to return 2 in that case, then the message will be automatically rejected even though the message was never scanned. (Because Windows returns 1 for a misspelled exit name, we chose 2=reject instead of the usual 1=reject.)
Once configured, spam scans take place whenever a virus scan takes place and no virus was detected, with two exceptions:
When downloading binary attachments via WA (virus scan only – spam filters are unlikely to do something meaningful with an .EXE file)
The minimum implementation for the REPORT call is to do the same as SCAN does. In addition, it is desirable to create an output file called spam.report in the same directory where the input file spam.tmp is located. There is no special format for this output file, but it is a good idea to start with a line saying whether or not the message was identified as spam, and give the score if the spam filter uses a score system. LISTSERV does not process the report, it just ends up being shown to a human. If no spam.report file is created by the exit, LISTSERV will use the exit string as a one-line report. If there is no exit string, LISTSERV will generate a hard-coded message.
4.4.2 Practical Application
To enable spam filtering in LISTSERV, you must install the third-party spam filter, provide a script that will scan messages using the third-party filter (if using SpamAssassin, you can use one of the L-Soft supplied scripts), and activate this script by making changes to the LISTSERV configuration. LISTSERV will then scan every message it processes, with a few exceptions, and reject messages identified as spam.
Optionally, you can also activate LISTSERV 14.3’s built-in blacklist/whitelist functionality (see the release notes for version 14.3 for more information). This feature provides an additional level of spam filtering and can also improve performance significantly, because spam filters like SpamAssassin can take up to 5-20 seconds to scan a message.
4.4.2.1 Step-by-Step Instructions
This section contains step-by-step instructions for configuring LISTSERV to use SpamAssassin using one of the L-Soft supplied scripts. Throughout this section, we will make the following assumptions:
SpamAssassin has already been installed and configured on a server that we will call spamd.example.com. This can, but does not have to be, the machine on which LISTSERV is installed. In particular, you can run LISTSERV on Windows and SpamAssassin on unix, and vice-versa.
 
Step 1 of 4: Install and test SpamAssassin client.
Unix: Compile spamc on the LISTSERV host, then copy it to a directory in LISTSERV’s path. To test it, use a command similar to the following:
$ spamc -c -d spamd.example.com < testmsg.txt
3.8/5.0
The flags you need to use may vary depending on your version of SpamAssassin and configuration. The response must be two numbers as shown above, but the numbers can be different than in the example (they are the SpamAssassin score of the test message). Any other response indicates an error. Refer to the spamc and spamd man pages for more information.
Windows: Download and install the spamc.exe executable from L-Soft, and place it in a directory in LISTSERV’s path – for instance, the LISTSERV\MAIN directory.
To test the client, issue the following command:
C:\> spamc -c -d spamd.example.com < testmsg.txt
3.8/5.0
The response must be two numbers as shown above, but the numbers can be different than in the example (they are the SpamAssassin score of the test message). Any other response indicates an error. In that case, make sure that spamd is configured to allow connections from the LISTSERV host.
 
Step 2 of 4: Install Perl or REXX (if not already available).
Unix: Install perl on the LISTSERV host, if not already installed.
Windows: Install a REXX interpreter, such as Regina REXX (http://regina-rexx.sourceforge.net/, Windows kit download available at http://prdownloads.sourceforge.net/regina-rexx/regina33.exe?download). When prompted to register .REXX as a path extension, you should do so. Alternatively, you can simply download REXX.EXE from L Soft and place it in the same directory where you saved spamc.exe.
 
Step 3 of 4: Install and configure SAEXIT script.
Unix: ftp://ftp.lsoft.com/LISTSERV/UNIX/CONTRIB/SAEXIT
Windows: ftp://ftp.lsoft.com/LISTSERV/Windows/CONTRIB/saexit.rexx
Edit the script to configure, at a minimum, the address of your SpamAssassin server. You may also want to change the other parameters.
Unix: ~listserv/home
Windows: C:\LISTSERV\MAIN
Windows: if you have not registered .REXX as a path extension when installing it in step 2 or if you downloaded it from the L Soft ftp site instead of installing the full kit, you will need to create a script called saexit.cmd in the C:\LISTSERV\MAIN directory containing the following three lines:
@echo off
rexx saexit.rexx %*
exit %ERRORLEVEL%
 
Step 4 of 4: Enable the saexit script.
To enable the script, add the following lines to LISTSERV’s configuration:
Unix: SPAM_EXIT="saexit"
export SPAM_EXIT
Windows: SPAM_EXIT=SAEXIT
Restart LISTSERV to make the change take effect, then mail a spam message to a test list to confirm that everything is working as it should.
 
Restrictions:
If you are using L-Soft’s Anti-Virus Station (AVS) to provide virus protection to a server for which F-Secure Anti-Virus is not available, the spam exit must be installed on the AVS server, not on the primary LISTSERV server. This is because message scanning is bypassed on a server that uses the AVS for this purpose.
The spam exit is implemented within LISTSERV’s message scanner, which is informally known as the “virus scanner,” because this was its original purpose. If the message scanner is disabled, for instance by setting the ANTI_VIRUS configuration parameter to 0, or by failing to install the maintenance LAK, both virus scanning and spam scanning are disabled. If ANTI_VIRUS is unset, LISTSERV will enable the message scanner if either virus scanning or spam scanning is configured and available.
 
VM and VMS
The spam exit is also available on VM and VMS, but there is no version of spamc for these systems. Since VM and VMS hosts normally use the AVS for message scanning, spam protection can be provided by simply installing the spam exit on the AVS server.
 
Advanced Configuration
At the list level, "Misc-Options= NO_SPAM_CHECK" can be used to disable spam scans for a particular list and its associated xxx-request address.
New statistical counters have been added for spam scans. They work just the same way as the virus counters.
The configuration parameter SPAM_MAXSIZE can be used to automatically accept messages larger than a certain size without wasting cycles scanning them. The rationale is that spam filters can take minutes to process very large messages, whereas spam messages are almost always very small. The following example sets the threshold to 512k:
VM: SPAM_MAXSIZE = 512
VMS: SPAM_MAXSIZE "512"
unix: SPAM_MAXSIZE=512
export SPAM_MAXSIZE
Win: SPAM_MAXSIZE=512
The default value is 256k. If set to 0, all messages will be scanned, which again could take considerable time. Messages that are not scanned do not count as a spam scan in the LISTSERV statistics.
4.5 A Practical Example: HAPPY99
In late 1998/early 1999 a worm called HAPPY99 surfaced on the Internet. In an attempt to keep the worm off of lists, an L-Soft engineer wrote the following exit in Regina REXX to run under the Windows NT version of LISTSERV 1.8d.
Warning: USE AT YOUR OWN RISK: THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS. L-SOFT DOES NOT MAKE ANY EXPRESS OR IMPLIED WARRANTY OF ANY KIND WHATSOEVER WITH RESPECT TO THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Neither L-Soft nor any of its employees, officers or agents will be liable for any direct, indirect or consequential damages, even if L-Soft had been advised of the possibility of such damage. No support is available from L-Soft or from the author for this program. If you give a copy of this program to someone else for their use, you must provide it with both the copyright notice and this paragraph intact. You are not authorized to make this program available for public access via anonymous FTP or by any other means of mass distribution.
First we wrote HAPPY99.REXX, which looks like this:
/* HAPPY99.REXX : Sample Windows NT list exit programming */
/* Added (0.1b) support for VBS.Freelink detection. */
versionno = '0.1b 1999/12/05'
/* By Nathan Brindle <nathan@lsoft.com> */
/* Copyright (c) 1999 L-Soft international, Inc. */
/* All rights reserved */
/*
USE AT YOUR OWN RISK: THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS. L-SOFT
DOES NOT MAKE ANY EXPRESS OR IMPLIED WARRANTY OF ANY KIND WHATSOEVER WITH
RESPECT TO THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Neither L-Soft nor any
of its employees, officers or agents will be liable for any direct,
indirect or consequential damages, even if L-Soft had been advised of the
possibility of such damage. No support is available from L-Soft or from the
author for this program. If you give a copy of this program to someone else
for their use, you must provide it with both the copyright notice and this
paragraph intact. You are not authorized to make this program available for
public access via anonymous FTP or by any other means of mass distribution.
*/
 
/*****************************************************************************/
/* WARNING WARNING WARNING */
/*****************************************************************************/
/* THIS PROGRAM IS NOT INTENDED TO BE USED WITHOUT MODIFICATION. IT IS ONLY A
SAMPLE OF HOW LIST EXIT PROGRAMMING MIGHT BE DONE UNDER WINDOWS NT. WHILE
THE EXAMPLES HAVE BEEN TESTED AND DO WORK, L-SOFT DOES NOT RECOMMEND SIMPLY
INSTALLING THIS EXIT AND USING IT WITHOUT MODIFICATION AS THE RESULTS WILL
PROBABLY NOT BE WHAT YOU EXPECT. NOTE CAREFULLY THAT THERE IS NO SUPPORT
AVAILABLE WHATSOEVER FOR THIS SAMPLE EXIT.
*/
 
/* List exits are documented in the Developer's Guide for LISTSERV, available
at L-Soft's FTP and WWW sites.
*/
 
/*****************************************************************************/
/* USER CONFIGURATION AREA STARTS HERE */
/* */
/* infile = the full path (drive, directory, filename) pointing to */
/* the exit.input file. This must be the path to \LISTSERV\MAIN */
/* and the filename must be 'exit.input'. */
/* outfile = the full path (drive, directory, filename) pointing to */
/* the exit.output file. This must be the path to \LISTSERV\MAIN */
/* and the filename must be 'exit.output'. */
/* tmpdir = the full path (drive and directory) pointing to LISTSERV's TMP */
/* directory. Typically this is something like C:\LISTSERV\TMP ; */
/* in any case it is the directory pointed to by the .SD D setting */
/* in SYSTEM.CFG. */
/* node = the value from NODE= in your SITE.CFG file, i.e., the name of */
/* your LISTSERV machine in DNS. */
/*****************************************************************************/
infile = 'e:\listserv\main\exit.input'
outfile = 'e:\listserv\main\exit.output'
tmpdir = 'E:\ListPlex\PEACH\TMP'
node = 'PEACH.EASE.LSOFT.COM' /* replace with NODE= value from site.cfg */
/*****************************************************************************/
/* USER CONFIGURATION AREA ENDS HERE */
/*****************************************************************************/
 
parms = linein(infile)
parse var parms epname listname more
 
if epname == 'POST_FILTER' then signal postfilter
 
/* otherwise, leave */
call lineout(outfile,'EXIT 0')
exit
 
/*****************************************************************************/
/* END OF PROGRAM if no epname is recognized */
/*****************************************************************************/
 
/*****************************************************************************/
/* POST_FILTER */
/*****************************************************************************/
postfilter:
 
/* Note that you could conceivably reject messages based on the value of 'c',*/
/* which is the total number of bytes in the message (including all headers, */
/* attachments, etc.). */
 
filtermsg = 'FALSE'
c = chars(tmpdir'\listserv.cmsut1')
msgin = charin(tmpdir'\listserv.cmsut1',1,c)
call charout(tmpdir'\listserv.cmsut1')
 
/* Grab Date: and Subject: headers */
msgdate = ''
msgsubject = ''
p = pos("Date:",msgin)
if p > 0 then do
e = pos('0d'x,msgin,p) - 1
msgdate = substr(msgin,p+5,(e-p)-4)
msgdate = strip(msgdate)
end
if msgdate == '' then msgdate = 'undated message'
else msgdate = 'message dated' msgdate
p = pos("Subject:",msgin)
if p > 0 then do
e = pos('0d'x,msgin,p) - 1
msgsubject = substr(msgin,p+8,(e-p)-7)
msgsubject = strip(msgsubject)
if left(msgsubject,1) == '0d'x then msgsubject = ''
end
if msgsubject == '' then msgsubject = 'with no subject'
else msgsubject = 'with subject "'||msgsubject||'"'
 
/* Check for uuencoded Happy99.exe virus */
filter = 'begin 644 Happy99.exe'
p = pos(translate(filter),translate(msgin))
if p > 0 then filtermsg = 'TRUE'
say '>>>' p
if filtermsg == 'TRUE' then do
explanation = 'Your' msgdate msgsubject 'sent'
explanation = explanation 'to the 'listname' mailing list has been'
explanation = explanation 'rejected because it appears to contain the'
explanation = explanation 'Happy99.EXE virus. It is strongly suggested'
explanation = explanation 'that you run an anti-virus program before'
explanation = explanation 'posting again.'
end
if filtermsg == 'TRUE' then signal done
 
/* deal with links.vbs (FreeLink) */
filter = 'Have fun with these links.'
p = pos(translate(filter),translate(msgin))
if p > 0 then filtermsg = 'TRUE'
say '>>>' p
if filtermsg == 'TRUE' then do
explanation = 'FREELINK: Your' msgdate msgsubject 'sent'
explanation = explanation 'to the 'listname' mailing list has been'
explanation = explanation 'rejected because it appears to contain the'
explanation = explanation 'VBS.Freelink virus. It is strongly suggested'
explanation = explanation 'that you run an anti-virus program before'
explanation = explanation 'posting again.'
end
if filtermsg == 'TRUE' then signal done
 
/* More filtering conditionals could be put in here */
 
done:
 
if filtermsg = 'FALSE' then call lineout(outfile,'EXIT 0')
if filtermsg = 'FALSE' then exit
 
/* Otherwise we've found reason to reject the message. */
/* Construct an explicit TELL directive to inform the user. The TELL */
/* directive has 3 parameters, which will be passed one per line. */
call lineout(outfile,'TELL3')
 
/* First parameter, the command originator, comes from the "more" */
/* variable. */
call lineout(outfile,word(more,1))
 
/* Next we pass second parameter, which we've already built above */
call lineout(outfile,explanation)
 
/* Third (optional) parameter, echo to the LISTSERV log */
call lineout(outfile,ECHO)
 
/* Now, reject the message. */
call lineout(outfile,'EXIT 1')
 
/* Finished. */
exit
 
/* end of program */
 
Then, we wrote the following HAPPY99.CMD file, and placed it in the \LISTSERV\MAIN directory. This CMD file, when executed by LISTSERV, calls the Regina interpreter and in turn tells it to run the HAPPY99.REXX file we wrote above.
REM be sure to change the directories to point to the right places!
c:\util\reskit\rexx.exe e:\listserv\main\happy99.rexx
Next, SITE.CFG was modified and the line
LIST_EXITS=HAPPY99
was added. LISTSERV was then stopped and restarted to pick up the change.
Finally, the exit was enabled for certain lists by adding
* Exit= HAPPY99
to the list headers.
Notes: The program makes no attempt to determine if the message actually contains a working copy of the virus. Its only function is to attempt to identify messages that MAY contain a working copy of the virus. Thus it may engender some false positives (but it's doubtful that anyone sending legitimate mail will send the specific strings searched for by the exit program).

The LISTSERV.CMSUT1 file that you search for evidence of the virus may not be edited "on the fly" to remove parts of the message--it is a read-only file for this purpose. You have only the option of accepting or rejecting the message in its entirety.

Given that the "classic" version of HAPPY99 is not sent as a MIME attachment, it is not possible to block it with the Attachments= list header keyword introduced in the 2000a level set release of LISTSERV 1.8d, and the exit is the only way to guard against it.

LISTSERV 1.8e and above, with its integrated anti-virus scanning and ability to filter encoded inline attachments removes the need for this particular exit, but the above remains a useful example of how to program an exit.
 
 

1
We should make it clear that the exit program MUST NOT have a file extension. If you write a MYEXIT exit program in perl, for instance, DO NOT name the exit MYEXIT.pl -- LISTSERV will not see it and you will see errors in the LISTSERV log each time an exit point is called for the list(s) in question. Thus the script simply should be named MYEXIT regardless of the language in which it is written. Since the script contains (or should contain) its own internal information about the interpreter being used to execute it (the "bang-hash" path in the first line), there is no overriding reason for the file to have an associative extension.


L-Soft international, Inc.
1-800-399-5449