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 version of LISTSERV 1.8d. 

Tip: It should be noted that modern versions of LISTSERV reject UUEncoded files by default when they are sent to lists; see the documentation for the Attachments= list header keyword for more information.  Thus this exit is technically obsolete, and is retained in the documentation only because it remains a fairly involved example of LISTSERV exit programming.

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.
 */        

/*****************************************************************************/
/*                   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 the exit searches 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 was not possible to block it with the original Attachments= list header keyword, and the exit was the only way to guard against it.

Modern versions of LISTSERV, 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 exit programming.

Tip: Windows users should be able to use more modern scripting languages, such as Perl or PowerShell, to write exits.  The key is that any exit you write for Windows MUST be called from a CMD file as has been documented above, regardless of the language in which it is written.