### Copyright (C) 1997-2000 John D. Hardin
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation; either version 2 of the License, or
### (at your option) any later version.
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### GNU General Public License for more details.
###
#
# <jhardin@wolfenet.com>
# $Id: html-trap.procmail,v 1.117 2000-08-08 20:24:01-07 jhardin Exp jhardin $
#
# Procmail snippet to defang active-content HTML tags to protect those
# people foolish enough to read their mail from a web browser or
# HTML-enabled mail client. Also mangles the attachment name on executable
# attachments to prevent attacks, at the cost of not being able to run
# programs from within your mail client - which you shouldn't do anyway.
# Also protects against excessively long filenames in attachments, which
# can cause nasty things to happen in some clients, and excessively long
# MIME headers, which may crash or allow exploits of some clients.
#
# All of the configuration options discussed below must be set in the
# procmail script that calls this filter, before this filter is called.
#
# If you wish to override the default list of executable extensions,
# set MANGLE_EXTENSIONS to a regexp (see where it is defined below for
# the proper syntax).
#
# If you wish to block specific executable or document filenames from
# attachments, define $POISONED_EXECUTABLES to point at a filename
# containing one filename per line, with no leading spaces or comments.
# Case is ignored. Wildcards are allowed, using a mishmash of filename
# globbing and regular expression syntax.
#
# Site policy for trapped messages can be specified within limited bounds.
# To redirect poisoned messages to a file, set $SECURITY_QUARANTINE to the
# name of the file. To notify administrator(s), set $SECURITY_NOTIFY and/or
# $SECURITY_NOTIFY_VERBOSE to a comma-delimited address list. The _VERBOSE
# recipients will get the whole message.
#
# If the trapped message cannot be saved in $SECURITY_QUARANTINE for any
# reason, the message will be bounced unless $SECURITY_QUARANTINE_OPTIONAL is
# set to any value.
#
# This performs limited scanning of attachments for M$ macros which contain
# possibly dangerous code (as opposed to specific strings from specific
# variants of specific exploits). To disable this scanning, set
# $DISABLE_MACRO_CHECK to anything. To adjust the score an attachment is
# considered "poisoned" at, set $POISONED_SCORE. The default is 25, which
# should be okay unless you see valid attachments with complex macros. Set
# $SCORE_HISTORY to a filename to track the scores on scanned documents.
# If you wish to do profiling before implementing poisoning, set
# $SCORE_ONLY to anything to scan and possibly record scores but not
# poison. This could also be accomplished by setting $POISONED_SCORE to a
# very high value (200?), which would trap attacks while still allowing
# profiling.
#
# If you wish to send a message to the author of a trapped message, set
# $SECURITY_NOTIFY_SENDER to any value. To replace the canned message that
# is sent to the author of a trapped message, set $SECURITY_NOTIFY_SENDER
# to point at a text file for the message body. No variable expansion is
# performed on the body of this message.
#
# This could also be extended fairly easily to allow virus-checking of
# attachments, assuming you have a virus-checker that will run under *nix.
#
# NOTES
#   Requires perl.
#   Attachment scanning requires mktemp and mimencode.
#
#   This is a non-delivering filter recipe unless $SECURITY_QUARANTINE is
#   set.
#
# INVOCATION
#   Insert
#     CONFIG_VARIABLE=some_value
#     INCLUDERC=html-trap.procmail
#   into your .procmailrc at the beginning or end.
#
#   For further details, particularly how to set up site-wide and mail hub
#   or relay filtering, visit:
#   ftp://ftp.rubyriver.com/pub/jhardin/antispam/procmail-security.html
#

# Size LINEBUF dynamically to deal with excessively large headers
:0 H
* 20480^0
* 1^1 .
{
  LINEBUF="$="
}

#---------------------------------------------------------------------------
# Grab some info for logging
#
NL="
"
SUBJ=""
FROM="unknown"

:0
* ^Subject[ 	]*:[ 	]+\/.+
{
  SUBJ=" in \"$MATCH\""
}

:0
* ^From[ 	]*:[ 	]+\/.+
{
  FROM="$MATCH"
  SUBJ="$SUBJ from $FROM"
}

TO=$LOGNAME

:0
* LOGNAME ?? ^root$
{
  # If $LOGNAME is root, we're probably running as a gateway filter:
  # get the "real" to name(s) out of the message headers.
  :0
  * ^To: +\/.*
  {
    TO="$MATCH"
  }
}

:0
* ^Message-ID:.*\/<[^>]+>
{
  MSGID="$MATCH"
  TO="$TO msgid=$MSGID"
}

SUBJ="$SUBJ to $TO
"

#---------------------------------------------------------------------------
# trap some excessively long RFC-822 headers
#

:0
* 1^1 ^\/(Mime-Version|Date|Resent-Date|Message-ID|Return-Path): ...........................................................................................................................................................................................................................................................*
{
  LOG="Trapped excessively long header$SUBJ"
  STATUS="STATUS: Message bounced."

  :0
  * SECURITY_QUARANTINE ?? [^ ]
  {
    STATUS="STATUS: Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient."
  }

  SECURITY_NOTIFY=${SECURITY_NOTIFY:-"postmaster"}

  :0
  * SECURITY_NOTIFY ?? [^ ]
  * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET
  {
    LOG="${NL}NOTIFY $SECURITY_NOTIFY${NL}"

    :0 h ci
    | ( \
        echo "To: $SECURITY_NOTIFY";\
        echo 'From: "Procmail Security daemon" <postmaster>';\
        echo 'Subject: SECURITY WARNING - possible email attack';\
        echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \
        echo ;\
        echo 'Trapped excessively long header:' ;\
        echo $MATCH;\
        echo ;\
        echo $STATUS;\
        echo ;\
        echo 'Headers from message:';\
        echo ;\
        sed -e 's/^/> /' ;\
      ) | $SENDMAIL -U $SECURITY_NOTIFY
  }

  :0
  * SECURITY_QUARANTINE ?? [^ ]
  {
    :0
    $SECURITY_QUARANTINE

    :0 e
    {
      # Argh! Quarantine failed!
      # notify administrator
      LOG="${NL}QUARANTINE FAILED!${NL}"

      # bounce it.
      EXITCODE=65

      :0 h
      * SECURITY_NOTIFY ?? [^ ]
      * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET
      | ( \
          echo "To: $SECURITY_NOTIFY";\
          echo 'From: "Procmail Security daemon" <postmaster>';\
          echo 'Subject: SECURITY WARNING - quarantine failed!';\
          echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \
          echo ;\
          echo 'Attempt to quarantine the following message in $SECURITY_QUARANTINE failed.';\
          echo 'Message has been bounced.';\
          echo 'Verify file access permissions:';\
          ls -l $SECURITY_QUARANTINE 2>&1 ;\
          echo ;\
          echo 'Headers from message:';\
          echo ;\
          sed -e 's/^/> /' ;\
        ) | $SENDMAIL -U $SECURITY_NOTIFY

    }
  }

  # bounce it.
  EXITCODE=65

  # zap it.
  :0
  /dev/null

}

#---------------------------------------------------------------------------
# Not all MIME can be sanitized...
#
:0
* ^Content-Type[ 	]*:.*multipart/((signed)|(encrypted));
{
  LOG="WARN: Cannot sanitize message due to signing or encryption$SUBJ"
}

#---------------------------------------------------------------------------
# Defang HTML active-content tags
#
# NB: In case you think the regexes should be /<[ 	]*TAG/, I suggest
# you *try* such tags in your browser first...
#
# Unfortunately the "on*" (e.g. "onload=") syntax is such that we can't
# reliably look for /onload="/ - there may be whitespace around the =.
# This isn't intended to be a full HTML parser, so we'll err on the side of
# safety by defanging everything, even though it may be outside of an HTML
# context.
#
# This keeps getting uglier as more and more holes are discovered.
#
# This will be folded into a better sanitizer Real Soon Now...
#

:0
* ! ^Content-Type[ 	]*:.*multipart/((signed)|(encrypted));
{
#----------- ALL OF THIS IS SKIPPED FOR SIGNED/ENCRYPTED MESSAGES

# "perl -e" has problems when run as root...
DROPPRIVS=YES

:0 B
* 1^1 \<(html|title|body|meta|app|script|object|embed|i?frame|style)
* 1^1 =(3d)?[ 	]*["'](&{|([a-z]+script|mocha):)
{

  LOG="Defanging active HTML content$SUBJ"

  :0 fw
  | perl -p -e '	#\
        if (/</) {	#\
                s/<(META|APP|SCRIPT|OBJECT|EMBED|FRAME|IFRAME|STYLE)/<DEFANGED_$1/gi;	#\
                s/\sSTYLE\s*=/ DEFANGED_STYLE=/gi;	#\
                s/\sOn(Abort|Blur|Change|Click|DblClick|DragDrop|Error|Focus|KeyDown|KeyPress|KeyUp|Load|MouseDown|MouseMove|MouseOut|MouseOver|MouseUp|Move|Reset|Resize|Select|Submit|Unload)/ DEFANGED_On$1/gi;	#\
        }	#\
        s/^\s*On(Abort|Blur|Change|Click|DblClick|DragDrop|Error|Focus|KeyDown|KeyPress|KeyUp|Load|MouseDown|MouseMove|MouseOut|MouseOver|MouseUp|Move|Reset|Resize|Select|Submit|Unload)/DEFANGED_On$1/i;	#\
        if (/["\047][^"\047\s]*&#x?[1-9][0-9a-f]/i) {	#\
                while (/["\047][^"\047\s]*&#((4[6-9]|5[0-8]|6[4-9]|[78][0-9]|9[07-9]|1[0-1][0-9]|12[0-2]))/) {	#\
                	$char = chr($1);	#\
                	s/&#$1;?/$char/g;	#\
                }	#\
                while (/["\047][^"\047\s]*&#(x(2[ef]|3[0-9a]|4[0-9a-f]|5[0-9a]|6[1-9a-f]|7[0-9a]))/i) {	#\
                	$char = chr(hex("0$1"));	#\
                	s/&#$1;?/$char/gi;	#\
                }	#\
        }	#\
        if (/["\047][^"\047\s]*%[2-7][0-9a-f]/i) {	#\
                while (/["\047][^"\047\s]*%((2[ef]|3[0-9a]|4[0-9a-f]|5[0-9a]|6[1-9a-f]|7[0-9a]))/i) {	#\
                	$char = chr(hex("0x$1"));	#\
                	s/%$1/$char/gi;	#\
                }	#\
        }	#\
        s/(["\047])([a-z]+script|mocha):/${1}DEFANGED_$2:/gi;	#\
        s/(["\047])&{/${1}DEFANGED_&{/g;	#\
        '
}

#---------------------------------------------------------------------------
# Mangle HTML and executable attachment filenames enough that they won't
# automatically execute, and limit the length of extremely long attachment
# filenames and MIME headers to prevent buffer overflows and client
# crashes (sigh). Adding ${$} to the mangling inserts a bit of randomness
# so that an active-HTML or BO exploit can't just look for an attachment
# named EXPLOIT.DEFANGED-EXE to get around the defanging.
#
# NOTE: the [ 	] has a tab embedded in it - DO NOT remove it...
#

:0
* ! MANGLE_EXTENSIONS ?? [^ ]
{
  MANGLE_EXTENSIONS='html?|exe|com|cmd|bat|pif|sc[rt]|lnk|do[ct]|xl[swt]|p[po]t|rtf|vb[se]?|hta|p[lm]|sh[bs]|hlp|chm|eml|ws[cfh]|ad[ep]|jse?|md[be]|ms[ip]|reg|asd'
}

# UUE attachments
:0 B
* ^begin[ 	]+([0-9]+)?[ 	]+[^ 	]+
{
  :0 B
  * ^begin[ 	]+([0-9]+)?[ 	]+\/[^ 	]....................................................................................................+$
  {
    LOG="Truncating extremely long attachment filename $MATCH$SUBJ"

    :0 fw
    | perl -p -e 'if (/^begin\s+[0-9]*\s/i) {	#\
            ($mode, $filen) = /^begin\s+([0-9]*)\s+(.{64}).*$/i;	#\
            $mode = "644" unless $mode;	#\
            s/^.*$/begin $mode $filen.../ if $filen;	#\
          }'
  }

  :0 B
  * $ ^begin[ 	]+([0-9]+)?[ 	]+.+\.(${MANGLE_EXTENSIONS})[ 	]*$
  {
    LOG="Sanitizing executable UUE attachments$SUBJ"

    :0 fw
    | perl -p -e '	#\
      if (($junk,$filen) = /^begin\s+([0-9]+\s+)?((\\.|[^"])+\.($ENV{"MANGLE_EXTENSIONS"}))\s*$/io) {	#\
        if ($specf = $ENV{"POISONED_EXECUTABLES"}) {	#\
          if (open(POISONED,$specf)) {	#\
            while (chomp($poisoned_spec = <POISONED>)) {  #\
              $poisoned_spec =~ s/^\s+//g;      #\
              $poisoned_spec =~ s/\s+$//g;      #\
              next unless $poisoned_spec; #\
              $poisoned_spec =~ s/([^\\])\./$1\\./g;      #\
              $poisoned_spec =~ s/\*/.*/g;        #\
              $poisoned_spec =~ s/\?/./g; #\
              warn "Checking against \"$poisoned_spec\"\n" if $ENV{"DEBUG"};	#\
              if ($filen =~ /^${poisoned_spec}$/i) {   #\
                warn " Trapped poisoned attachment \"$filen\".\n";	#\
                print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                print "X-Content-Security: REPORT: Trapped poisoned attachment \"$filen\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"};	#\
                print "\n";	#\
                print "SECURITY WARNING!\n";	#\
                print "The mail system has detected that the following\n";	#\
                print "attachment may contain hazardous executable code,\n";	#\
                print "is a suspicious file type or has a suspicious file name.\n";	#\
                print "Contact your system administrator immediately!\n\n";	#\
                print "SUSPICIOUS ATTACHMENT: ";       #\
                last;     #\
              }	#\
            }	#\
            close(POISONED);	#\
          } else {	#\
            warn " Unable to open poisoned-executables file \"$specf\".\n";	#\
          }	#\
        }	#\
        warn " Mangling executable filename \"$filen\".\n";	#\
        $filen =~ s/\.([a-z]+)$/.${$}DEFANGED-$1/i;	#\
        print "begin 666 $filen\n";     #\
        $_ = "";        #\
      } #\
    ' 2>> $LOGFILE
  }
}

# MIME attachments
:0
* 1^0 ^Content-Type[ 	]*:.*(application|multipart)/[^ ]*;
* 1^0 ^Content-Disposition[ 	]*:.*attachment
{
  LOG="Sanitizing MIME attachment headers$SUBJ"

  # Due to procmail not unwrapping MIME attachment headers,
  # (they're in the message body) this perl script has to run against
  # *every* message with MIME attachments to ensure security. Sorry.

  # NOTE: I don't use the CPAN MIME module in order to keep this as simple
  # as possible and to keep it self-contained (i.e. everything is *right here*).
  # (Attachment scanning breaks this. Which is worse - mimencode or Mime::Base64?)

  # Make sure $LOGFILE exists so the shell doesn't barf
  LOGFILE=${LOGFILE:-"/dev/null"}

  # If you get "Out of memory" errors in your procmail log, try changing to
  # the following:
  # :0 fw
  # | ulimit -d 15000; perl -p -e '     #\

  POISONED_SCORE=${POISONED_SCORE:-25}

  :0 fw
  | perl -p -e '	#\
      $pasthdr = 1 if /^\s*$/;	#\
      unless ($pasthdr) {	#\
        if (($type) = /^Content-Type\s*:\s.*(application|multipart)\/\S+;/i) {	#\
          $wanthdr = 1;	#\
          print "X-Security: MIME headers sanitized on ", $ENV{"HOST"}, "\n";	#\
          print "\tSee http://www.wolfenet.com/~jhardin/procmail-security.html\n";	#\
          print "\tfor details. \$Revision: 1.117 $x\$Date: 2000-08-08 20:24:01-07 $x\n";	#\
          if ($type =~ /application/i) {	#\
            $inmimehdr = 1;	#\
          }	#\
        } elsif (/^\S/) {	#\
          $wanthdr = 0;	#\
        }	#\
        if ($wanthdr) {	#\
          if (($mimeboundary) = /boundary\s*=\s*((".+")|([^"]\S+))/i) {	#\
            $mimeboundary =~ s/(^"|"$)//g;	#\
            $rawboundary = $mimeboundary;	#\
            if ($boundarytoolong = (length($mimeboundary) > 80)) {	#\
              warn " Truncating long MIME body-part boundary string.\n";	#\
              $newboundary = substr($mimeboundary,0,64);	#\
              $mimeboundary = quotemeta($mimeboundary);	#\
              s/${mimeboundary}/${newboundary}/;	#\
              $rawboundary =~ s/${mimeboundary}/${newboundary}/;	#\
            } else {	#\
              $mimeboundary = quotemeta($mimeboundary);	#\
            }	#\
          }	#\
        }	#\
      }	#\
      if ($mimeboundary || $inmimehdr) {	#\
        if (/^\s*$/) {	#\
          $inmimehdr = 0;	#\
        } elsif (/^--${mimeboundary}(--)?$/o) {	#\
          $inmimehdr = 1;	#\
          $check_attachment = 0;	#\
          s/${mimeboundary}/${newboundary}/ if $boundarytoolong;	#\
        } elsif (!$inmimehdr && $check_attachment) {	#\
          $check_attachment = 0;	#\
          if ($destf = `mktemp /tmp/mailchk.XXXXXX`) {	#\
            chomp($destf);	#\
            if (open(DECODE,"|mimencode -u -o $destf")) {	#\
              do {	#\
                print $_;	#\
                print DECODE $_;	#\
                $_ = <>;	#\
                $lastline = $_;	#\
              } until (/^\s*$/ || /^--/);	#\
              close(DECODE);	#\
              # Run virus-checker here.	#\
              open(ATTCH,"< $destf");	#\
              $msapp = $score = 0;	#\
              while (<ATTCH>) {	#\
                if ($msapp) {	#\
                  $score+= 99 if /\000VirusProtection/i;	#\
                  $score+= 99 if /\000select\s[^\000]*shell\s*\(/i;	#\
                  $score+= 9 if /\000regedit/i;	#\
                  $score+= 9 if /\000SaveNormalPrompt/i;	#\
                  $score+= 9 if /\000Outlook.Application\000/i;	#\
                  $score+= 4 if /\000ID="{[-0-9A-F]+$/i;	#\
                  $score+= 4 if /\000CreateObject/i;	#\
                  $score+= 4 if /(\000|\004)([a-z0-9_]\.)*(Autoexec|Workbook_(Open|BeforeClose)|Document_(Open|New|Close))/i;	#\
                  $score+= 4 if /(\000|\004)(Logon|AddressLists|AddressEntries|Recipients|Subject|Body|Attachments|Logoff)/i;	#\
                  $score+= 2 if /\000Shell/i;	#\
                  $score+= 2 if /\000Options/i;	#\
                  $score+= 2 if /\000CodeModule/i;	#\
                  $score+= 2 if /\000([a-z]+\.)?Application\000/i;	#\
                  $score+= 2 if /(\000|\004)stdole/i;	#\
                  $score+= 2 if /(\000|\004)NormalTemplate/i;	#\
                  $score+= 1 if /\000ThisWorkbook\000/i;	#\
                  $score+= 1 if /\000PrivateProfileString/i;	#\
                  $score+= 1 if /\000ID="{[-0-9A-F]+}"/i;	#\
                  $score+= 1 if /(\000|\004)(ActiveDocument|ThisDocument)/i;	#\
                  $score+= 1 if /\000\[?HKEY_(CLASSES_ROOT|CURRENT_USER|LOCAL_MACHINE)/;	#\
                } else {	#\
                  if (/\000(Microsoft (Word Document|Excel Worksheet|Excel|PowerPoint)|MSWordDoc|Word\.Document\.[0-9]+|Excel\.Sheet\.[0-9]+)\000/) {	#\
                    $msapp = 1;	#\
                    seek(ATTCH,0,0);	#\
                  }	#\
                }	#\
              }	#\
              close(ATTCH);	#\
              unlink($destf);	#\
              if ($histfile = $ENV{"SCORE_HISTORY"}) {	#\
                if (open(HIST,">>$histfile")) {	#\
                  print HIST "score=$score msgid=".$ENV{"MSGID"}." from=".$ENV{"FROM"}."\n";	#\
                  close HIST;	#\
                }	#\
              }	#\
              $poison_score = $ENV{"POISONED_SCORE"};	#\
              $poison_score = 5 if $poison_score < 5;	#\
              if ($score > $poison_score && !$ENV{"SCORE_ONLY"}) {	#\
                warn " POSSIBLE MACRO EXPLOIT: Score=$score\n";	#\
                print "\n\n--$rawboundary\n";	#\
                print "Content-Type: TEXT/PLAIN;\n";	#\
                print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                print "X-Content-Security: REPORT: Trapped poisoned Microsoft attachment\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"};	#\
                print "Content-Description: SECURITY WARNING\n\n";	#\
                print "SECURITY WARNING!\n";	#\
                print "The mail delivery system has detected that the preceding\n";	#\
                print "document attachment appears to contain hazardous macro code.\n";	#\
                print "Macro Scanner score: $score\n";	#\
                print "Contact your system administrator immediately!\n\n";	#\
              }	#\
              if ($lastline =~ /^--${mimeboundary}(--)?$/o) {	#\
                $inmimehdr = 1;	#\
                $check_attachment = 0;	#\
                $lastline =~ s/${mimeboundary}/${newboundary}/ if $boundarytoolong;	#\
              }	#\
              print $lastline;	#\
            } else {	#\
              warn "  Cannot decode attachment: $!  - is mimencode installed?\n";	#\
            }	#\
          } else {	#\
            warn "  Cannot extract attachment: $!  - is mktemp installed?\n";	#\
          }	#\
        }	#\
        if ($inmimehdr || $hdrcnt) {	#\
          if (/^(\s+\S|(file)?name)/) {	#\
            s/^\s*/ /;	#\
            s/^\s*// if $hdrtxt =~ /"[^"]*[^;]$/;	#\
            s/\s*\n$//;	#\
            $hdrtxt .= $_;	#\
            $_ = "";	#\
          } else {	#\
            if ($hdrtxt) {	#\
              $hdrtxt =~ s/([^\\])\\"/\1\\ÿ/g;	#\
              if ($hdrtxt =~ /`\s*`/) {	#\
                warn " Fixing double backquotes.\n";	#\
                $hdrtxt =~ s/`\s*`/\\"/g;	#\
              }	#\
              if ($hdrtxt =~ /^[-\w]+\s*:.*name\s*=\s*"[^"]+$/i) {	#\
                warn " Fixing missing close quote on filename.\n";	#\
                $hdrtxt .= "\"";	#\
              }	#\
              while (($junk,$filen) = $hdrtxt =~ /^Content-[-\w]+\s*:[^"]*("[^"]*"[^"]+)*name\s*=\s*([^"\s][^;]+)/i) {	#\
                warn " Fixing unquoted filename \"$filen\".\n";	#\
                $newfilen = $filen;	#\
                $newfilen =~ s/\"/\\"/g;	#\
                if ($newfilen =~ /\([^)]*\)/) {	#\
                  warn " Filename contains embedded RFC822 comment - removing.\n";	#\
                  $newfilen =~ s/\([^)]*\)//g;	#\
                }	#\
                $filen = quotemeta($filen);	#\
                $hdrtxt =~ s/name\s*=\s*${filen}/name="$newfilen"/ig;	#\
              }	#\
              while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]{64})[^"]{16,}"/i) {	#\
                warn " Truncating long filename.\n";	#\
                $hdrtxt =~ s/name\s*=\s*"[^"]{80,}"/name="$filen..."/i;	#\
              }	#\
              if (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]+\.(do[ct]|xl[swt]|p[po]t|rtf)(\?=)?)"/i) {	#\
                warn " Scanning \"$filen\".\n";	#\
                if (!$poisoned && ($specf = $ENV{"POISONED_EXECUTABLES"})) {	#\
                  if (open(POISONED,$specf)) {	#\
                    while (chomp($poisoned_spec = <POISONED>)) {  #\
                      $poisoned_spec =~ s/^\s+//g;      #\
                      $poisoned_spec =~ s/\s+$//g;      #\
                      next unless $poisoned_spec; #\
                      $poisoned_spec =~ s/([^\\])\./$1\\./g;      #\
                      $poisoned_spec =~ s/\*/.*/g;        #\
                      $poisoned_spec =~ s/\?/./g; #\
                      $poisoned_spec .= "(\\?=)?"; #\
                      warn "Checking against \"$poisoned_spec\"\n" if $ENV{"DEBUG"};	#\
                      if ($filen =~ /^${poisoned_spec}$/i) {   #\
                        warn " Trapped poisoned document \"$filen\".\n";	#\
                        $poisoned = 1;	#\
                        print "Content-Type: TEXT/PLAIN;\n";	#\
                        print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                        print "X-Content-Security: REPORT: Trapped poisoned Microsoft attachment \"$filen\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                        print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"};	#\
                        print "Content-Description: SECURITY WARNING\n\n";	#\
                        print "SECURITY WARNING!\n";	#\
                        print "The mail system has detected that the following\n";	#\
                        print "attachment may contain hazardous macro code,\n";	#\
                        print "is a suspicious file type or has a suspicious file name.\n";	#\
                        print "Contact your system administrator immediately!\n";	#\
                        print "Macro Scanner score: 0 (not scanned due to poisoning policy)\n\n";	#\
                        last;     #\
                      }	#\
                    }	#\
                    close(POISONED);	#\
                  } else {	#\
                    warn " Unable to open poisoned-executables file \"$specf\".\n";	#\
                  }	#\
                }	#\
                $check_attachment = 1 unless $ENV{"DISABLE_MACRO_CHECK"};	#\
              }	#\
              $mangle_mime_type = 0;	#\
              while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]+\.($ENV{"MANGLE_EXTENSIONS"})(\?=)?)"/io) {	#\
                if (!$poisoned && ($specf = $ENV{"POISONED_EXECUTABLES"})) {	#\
                  if (open(POISONED,$specf)) {	#\
                    while (chomp($poisoned_spec = <POISONED>)) {  #\
                      $poisoned_spec =~ s/^\s+//g;      #\
                      $poisoned_spec =~ s/\s+$//g;      #\
                      next unless $poisoned_spec; #\
                      $poisoned_spec =~ s/([^\\])\./$1\\./g;      #\
                      $poisoned_spec =~ s/\*/.*/g;        #\
                      $poisoned_spec =~ s/\?/./g; #\
                      $poisoned_spec .= "(\\?=)?"; #\
                      warn "Checking against \"$poisoned_spec\"\n" if $ENV{"DEBUG"};	#\
                      if ($filen =~ /^${poisoned_spec}$/i) {   #\
                        warn " Trapped poisoned executable \"$filen\".\n";	#\
                        $poisoned = 1;	#\
                        print "Content-Type: TEXT/PLAIN;\n";	#\
                        print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                        print "X-Content-Security: REPORT: Trapped poisoned executable \"$filen\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"};	#\
                        print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"};	#\
                        print "Content-Description: SECURITY WARNING\n\n";	#\
                        print "SECURITY WARNING!\n";	#\
                        print "The mail system has detected that the following\n";	#\
                        print "attachment may contain hazardous executable code,\n";	#\
                        print "is a suspicious file type or has a suspicious file name.\n";	#\
                        print "Contact your system administrator immediately!\n\n";	#\
                        last;     #\
                      }	#\
                    }	#\
                    close(POISONED);	#\
                  } else {	#\
                    warn " Unable to open poisoned-executables file \"$specf\".\n";	#\
                  }	#\
                }	#\
                warn " Mangling executable filename \"$filen\".\n";	#\
                $newfilen = $filen;	#\
                $newfilen =~ s/\.([a-z0-9]+(\?=)?)$/.${$}DEFANGED-$1/i;	#\
                $filen = quotemeta($filen);	#\
                $hdrtxt =~ s/name\s*=\s*"?${filen}"?/name="$newfilen"/ig;	#\
                $mangle_mime_type = 1;	#\
              }	#\
              if ($mangle_mime_type && $hdrtxt =~ /^Content-Type:\s/i) {	#\
                ($oct) = $hdrtxt =~ /^Content-Type:.*\s(\S+\/\S+;?)/i;	#\
                unless ($oct =~ /application\/octet-stream;/i) {	#\
                  print "X-Content-Security: original Content-Type was $oct\n";	#\
                  $oct = quotemeta($oct);	#\
                  $hdrtxt =~ s/${oct}/application\/octet-stream;/i;	#\
                }	#\
              }	#\
              if ($mangle_mime_type && $hdrtxt =~ /\sx-mac-\S+/i) {	#\
                $eudora = "";	#\
                while (($eh) = $hdrtxt =~ /(\sx-mac-\S+\s*=\s*\S+;?)/i) {	#\
                  $eudora .= $eh;	#\
                  $eh = quotemeta($eh);	#\
                  $hdrtxt =~ s/${eh}//i;	#\
                }	#\
                print "X-Content-Security: removed$eudora\n";	#\
              }	#\
              if (($junk) = $hdrtxt =~ /^Content-Type\s*:\s+(.{128}).{100,}$/i) {	#\
                warn " Truncating long Content-Type header.\n";	#\
                $junk =~ s/"/\\"/g;	#\
                $hdrtxt = "Content-Type: X-BOGUS\/X-BOGUS; originally=\"$junk...\"";	#\
              }  elsif (($junk) = $hdrtxt =~ /^Content-Description\s*:\s+(.{128}).{100,}$/i) {	#\
                warn " Truncating long Content-Description header.\n";	#\
                $hdrtxt = "Content-Description: $junk...";	#\
              }  elsif (($junk) = $hdrtxt =~ /^Content-[-\w]+\s*:\s+(.{128}).{100,}$/i) {	#\
                warn " Truncating long MIME header.\n";	#\
                $junk =~ s/"/\\"/g;	#\
                $hdrtxt =~ s/^Content-([-\w]+)\s*:.*$/X-Overflow: Content-$1; originally="$junk..."/i;	#\
              }	#\
              #if ($hdrtxt =~ /^Content-Transfer-Encoding\s*:\s+base64/i) {	#\
              #  $check_attachment = 1;	#\
              #}	#\
              $hdrtxt =~ s/\\ÿ/\\"/g;	#\
              print $hdrtxt, "\n";	#\
              $hdrtxt = "";	#\
            }	#\
            if (/^\S/) {	#\
              s/\s*\n$//;	#\
              $hdrtxt = $_;	#\
              $_ = "";	#\
              $hdrcnt++;	#\
            } else {	#\
              $hdrcnt = 0;	#\
              $hdrtxt = "";	#\
            }	#\
          }	#\
        } else {	#\
          $poisoned = 0;	#\
        }	#\
      }	#\
    ' 2>> $LOGFILE
}

:0 HB
* ^X-Content-Security: (NOTIFY|QUARANTINE)
{
  :0
  * 1^0 SECURITY_NOTIFY ?? [^ ]
  * 1^0 SECURITY_NOTIFY_VERBOSE ?? [^ ]
  {
    # Notify administration and sender of the attack

    STATUS="STATUS: Message delivered to $TO"
    STATUS_PUBLIC="STATUS: Message delivered."
    REPORT="REPORT: No details available."
    SCORE="REPORT: Not a document, or already poisoned by filename. Not scanned for macros."

    :0
    * SECURITY_QUARANTINE ?? [^ ]
    {
      STATUS="STATUS: Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient."
      STATUS_PUBLIC="STATUS: Message quarantined, not delivered to recipient."
    }

    :0 B
    * ^\/Macro Scanner score: [1-9][0-9]+
    {
      SCORE="REPORT: $MATCH"
    }

    :0 HB
    * ^X-Content-Security: \/REPORT: .*
    {
      REPORT="$MATCH"
    }

    :0
    * SECURITY_NOTIFY ?? [^ ]
    * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET
    {
      LOG="${NL}NOTIFY $SECURITY_NOTIFY${NL}"

      :0 h ci
      | ( \
          echo "To: $SECURITY_NOTIFY";\
          echo 'From: "Procmail Security daemon" <postmaster>';\
          echo 'Subject: SECURITY WARNING - possible email attack';\
          echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \
          echo ;\
          echo $REPORT;\
          echo $SCORE;\
          echo $STATUS;\
          echo ;\
          echo 'Headers from message:';\
          echo ;\
          sed -e 's/^/> /' ;\
        ) | $SENDMAIL -U $SECURITY_NOTIFY
    }

    :0
    * SECURITY_NOTIFY_VERBOSE ?? [^ ]
    * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET
    {
      LOG="${NL}NOTIFY $SECURITY_NOTIFY_VERBOSE${NL}"

      :0 hb ci
      | ( \
          echo "To: $SECURITY_NOTIFY_VERBOSE";\
          echo 'From: "Procmail Security daemon" <postmaster>';\
          echo 'Subject: SECURITY WARNING - possible email attack';\
          echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \
          echo ;\
          echo $REPORT;\
          echo $SCORE;\
          echo $STATUS;\
          echo ;\
          echo 'Message:';\
          echo ;\
          sed -e 's/^/> /' ;\
        ) | $SENDMAIL -U $SECURITY_NOTIFY_VERBOSE
    }

    :0 H
    * SECURITY_NOTIFY_SENDER ?? [^ ]
    * !^FROM_DAEMON
    * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET
    {
      LOG="${NL}NOTIFY SENDER${NL}"

      :0 h ci
      | ( \
          formail -r \
                  -I 'From: "Procmail Security daemon" <postmaster>'\
                  -I "Bcc: $SECURITY_NOTIFY" \
                  -I "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET" \
          ;\
          echo "";\
          if [ -f "$SECURITY_NOTIFY_SENDER" -a -s "$SECURITY_NOTIFY_SENDER" -a -r "$SECURITY_NOTIFY_SENDER" ] ;\
          then \
                echo 'Regarding your message to';\
                echo $TO;\
                echo ;\
                cat $SECURITY_NOTIFY_SENDER; \
          else \
                echo '*** SECURITY WARNING ***';\
                echo 'Our email gateway has detected that your message to';\
                echo $TO;\
                echo 'may contain hazardous embedded scripting or attachments.';\
                echo 'Please notify your system administrator by phone right away.';\
                echo 'You should make sure your virus signature list';\
                echo 'is up-to-date and then rescan your computer.';\
                echo '';\
                echo 'If the macro scanner score is large yet your virus scanner reports';\
                echo 'that the document is not infected, try saving it using a different';\
                echo 'format that will completely strip out all macros, such as RTF.';\
          fi ;\
          echo ;\
          echo $REPORT;\
          echo $SCORE;\
          echo $STATUS_PUBLIC;\
          echo ;\
          echo '--';\
          echo 'Message sanitized on' $HOST;\
          echo 'See http://www.wolfenet.com/~jhardin/procmail-security.html for details.';\
          echo ;\
        ) | $SENDMAIL -oi -t

    }
  }

  :0
  * SECURITY_QUARANTINE ?? [^ ]
  {
    :0
    $SECURITY_QUARANTINE

    :0 e
    * ! SECURITY_QUARANTINE_OPTIONAL ?? [^ ]
    {
      # Argh! Quarantine failed, and not explicitly marked as optional!
      # Bounce message, and notify administrator
      LOG="${NL}QUARANTINE FAILED!${NL}"
      EXITCODE=65

      :0 h
      * SECURITY_NOTIFY ?? [^ ]
      * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET
      | ( \
          echo "To: $SECURITY_NOTIFY";\
          echo 'From: "Procmail Security daemon" <postmaster>';\
          echo 'Subject: SECURITY WARNING - quarantine failed!';\
          echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \
          echo ;\
          echo 'Attempt to quarantine the following message in $SECURITY_QUARANTINE failed.';\
          echo 'Message has been bounced.';\
          echo 'Verify file access permissions:';\
          ls -l $SECURITY_QUARANTINE ;\
          echo ;\
          echo $REPORT;\
          echo $SCORE;\
          echo ;\
          echo 'Headers from message:';\
          echo ;\
          sed -e 's/^/> /' ;\
        ) | $SENDMAIL -U $SECURITY_NOTIFY

      # zap it, just in case
      :0
      /dev/null
    }
  }
}

}  # ---- END OF SIGNED/ENCRYPTED SKIP

#eof
