Kiegészítés az Exchange 2003 IMF-hez

Az informatikai világunk egyik legbosszantóbb jelensége a spam (én részemről fa testápolóval kezelném az előállítókat és a megrendelőket). Valamilyen módon kénytelenek vagyunk védekezni ellene. A saját rendszerünkben linux alapú tűzfal van így néhány évvel ezelőtt amikor a spam kezdett problémás méreteket ölteni kézenfekvőnek látszott, hogy a linux tűzfalra kerüljön egy spam- és vírusszűrésre alkalmas SMTP gateway. Akkoriban a SendMail, RAW Antivirus, ASSP hármas mellett döntöttem. Ez kisebb problémákkal elég jól elmuzsikált, az ASSP igen nagy hatékonysággal szűrt. Nagyjából egy fél évvel ezelőtt egyértelművé vált, hogy ezt a tűzfalat és a rajta futó eszközök jó részét le kell cserélnem. Az okok:
– A SendMail egy konfigurálhatatlan borzalom, ha komolyabb beállításokról van szó, akkor a dokumentáltsága nulla
– A RAW gyártóját korábban felvásárolta a Microsoft ezért idővel lecseréltük a DrWeb-re, de alapvetően ingyenes megoldást akartam
– Az ASSP mint szűrő jó volt, de teljesítményproblémái adódtak, ha nagy tömegű levelet kellett feldolgozni
Nagyjából három hónapja meg is ejtettem a cserét. PostFix, Amavis, ClamAV, SpamAssassin lett belőle. A dolog jól is működött a SpamAssasint leszámítva. Biztos vagyok benne, hogy a mi tudásunk kevés, de képtelenek voltunk a SpamAssassinből valami használható hatékonyságot kiverni. Mára teljesen megelégeltem a működését és kerestem megoldást a lecserélésére.
Kézenfekvő megoldásként az Exchange Inteligent Message Filterére gondoltam. Már korábban is kacérkodtam vele, de elvetettem, mert nem felelt meg az igényeimnek. Alapvető kérdésnek tartom, hogy mi történik a spamnek minősített levelekkel. Az IMF ezekkel a következőt tudja tenni:
– Eldobja
– Archiválja
– Kézbesíti
Az utolsó lehetőség jó lenne, de a kliens oldalon nem minden esetben tudom megoldani ezen levelek feldolgozását a headerben található X-SCL mező alapján, ezért azt szerettem volna elérni, hogy ugyanúgy ahogy az ASSP, vagy a SpamAssassin esetén a levél tárgymezejébe kerüljön egy [SPAM] bejegyzés amire könnyen tudok egy olyan szabályt gyártani ami a Levélszemét mappába pakolja a levelet.
Ráadásul a kézbesítés azért sem jöhetett szóba mert a cégcsoport egy másik tagja (közös Exchange szervezet) úgy döntött, hogy megkockáztatja, hogy törli a beérkezett spameket vállalva ezzel annak a kockázatát, hogy fals pozitív levelek is törlésre kerülnek.
Végiggondolva az egész helyzetet arra jutottam, hogy egy kis programozással megoldom a kérdést.
A dolog a következőképpen működik:
Az IMF csücsül a Default SMTP Virtual Serveren és pakolja a spamet egy archív mappába. Egy script folyamatosan figyeli az archív mappát és az oda érkező leveleket feldolgozza a következőképpen:
– Megnézi, hogy létezik-e a címzett az AD-ban. Ha nem akkor eldobja a levelet (spamre nem küldünk NDR-t)
– Ha létezik akkor beírja a tárgyba a [SPAM] feliratot és egy második SMTP Virtual Server Pickup könyvtárába rakja a levelet (azért nem a Defaultba, mert így nem találkozik újra az IMF-el. Egyébként végtelen ciklus lenne belőle)
 
A kód (imfc.js):
 
/* 
 THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 EITHER EXPRESSED OR IMPLIED,  INCLUDING BUT NOT LIMITED TO THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.

 This code is free for both personal and commercial use, but you are
 expressly forbidden from selling.

 **************************************************************************

 FILE NAME:
   imfc.js

 DESCRIPTION:
   Microsoft Exchange Inteligent Message Filter SPAM Categorizer
   
 COPYRIGHT:
   Copyright (c) Zoltán Gömöri. 2006.
   All rights reserved.
   
 NOTES:
   The original version of this source code and the article that
   includes this source code can be found at:
     http://www.gomori.hu

 CREATED:
   2006.12.13
   
 LAST MODIFIED:
   2006.12.14
   
 VERSION:
   v1.0 Build 1
   
TO DO:

 **************************************************************************
*/

// Global variables
var GLOBAL_PICKUP_PATH = "C:\\Program Files\\Exchsrvr\\Mailroot\\vsi 2\\PickUp";

// ADO Constants
var adTypeBinary = 1;
var adTypeText = 2;
var adStateClosed = 0;
var adWriteLine = 1;
var adReadAll = -1;
var adReadLine = -2;

// Object declarations
String.prototype.trim = _String_trim;

// Get the event object
var MsgEvent = TargetEvent.TargetInstance; // This is the placeholder of the msg file provided by the WMI
var MsgFileName = MsgEvent.Drive + MsgEvent.Path + MsgEvent.FileName + "." + MsgEvent.Extension;

// Load message file
var Msg = LoadMsgFile(MsgFileName);

// Build the X-Receivers array
var EnvelopeRecipients = GetMsgXRecipients(Msg);

// Search the X-Receivers in the AD. Remove the nonexistent ones
for(i = EnvelopeRecipients.length-1; i >= 0; i--)
    if(!FindExMbx(EnvelopeRecipients[i]))
        EnvelopeRecipients.splice(i,1);

// If the message has existing recipient
if(EnvelopeRecipients.length > 0)
{
    // put [SPAM] into the subject
    if(Msg.Subject.indexOf("[SPAM]") == -1)
        Msg.Subject = "[SPAM] " + Msg.Subject;
    // remove the original X-Receiver fields   
    Msg.Fields.Delete("urn:schemas:mailheader:x-receiver");
    Msg.Fields.Update();
    // add recipients
    var MsgStream = new ActiveXObject("ADODB.Stream");
    MsgStream.Type = adTypeText;
    MsgStream.Charset = "iso-8859-1";
    MsgStream.Open();
    for(i=0;i<EnvelopeRecipients.length;i++)
        MsgStream.WriteText("x-receiver: " + EnvelopeRecipients[i], adWriteLine);
    MsgStream.Position = 0;
    MsgStream.Type = adTypeBinary;
    MsgStream.Position = MsgStream.Size;
    Msg.GetStream().CopyTo(MsgStream);
    // Save the message to 
    MsgStream.SaveToFile(GLOBAL_PICKUP_PATH + "\\" + MsgEvent.FileName + "." + MsgEvent.Extension);
}
// Remove the file
var FSO = new ActiveXObject("Scripting.FileSystemObject");
FSO.DeleteFile(MsgFileName);

/*
    GetMsgXRecipients - Generates the array of recipents from the x-receiver
                        header field os a CDO.Message object
    Parameters:
        Msg - The CDO.Message object
    Returns:
        Array of the recipient e-mail addresses
*/

function GetMsgXRecipients(Msg)
{
    var retvalue = new Array();
    var i = 0;
    var MsgStream = Msg.GetStream();
    var Buffer;
    MsgStream.Type = adTypeText;
    for(Buffer = MsgStream.ReadText(adReadLine); !MsgStream.EOS && Buffer.length != 0; Buffer = MsgStream.ReadText(adReadLine))
        if(Buffer.substr(0,11) == "x-receiver:")
        {
            retvalue[i] = Buffer.substr(11).trim();
            i++;
        }
    return retvalue;
}

/*
    LoadMsgFile - This function loads an eml file into a CDO.Message object
    Parameters:
        FileName - Name of the eml file to load
    Returns:
        The CDO.Message object
    To Do:
        Error handling
*/

function LoadMsgFile(FileName)
{
    var Msg;
    var MsgStream;
    Msg = new ActiveXObject("CDO.Message");
    MsgStream = new ActiveXObject("ADODB.Stream");
    MsgStream.Type = adTypeBinary;
    MsgStream.Open();
    MsgStream.LoadFromFile(FileName);
    Msg.DataSource.OpenObject(MsgStream,"_Stream");
    return Msg;
}


/*
    FindExMbx - This function search for an AD Object with Exchange Mailbox
                (Additionally Public folders, Distribution groups)
    Parameters:
        email - E-Mail address to find
    Returns:
        true if found
*/
function FindExMbx(email)
{
    var i;
    var retvalue = false;
    var MailArr;
    // Get actual AD domain
    var rootDSE = GetObject("LDAP://rootDSE");
    var ForestRoot = rootDSE.Get("rootDomainNamingContext");
    // ADDN = rootDSE.Get("defaultNamingContext");

    // Open an ADSI OleDB connection to the AD
    var objConnection = new ActiveXObject("ADODB.Connection")
    objConnection.Open("Provider=ADsDSOObject;");
    var objCommand = new ActiveXObject("ADODB.Command");
    objCommand.ActiveConnection = objConnection;
    // Find the user object
    objCommand.CommandText = "<GC://" + ForestRoot+ ">;" +
                             "(&(proxyAddresses=smtp:" + email + ")(|(&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*)))(objectCategory=group)(objectCategory=publicFolder)(objectCategory=msExchDynamicDistributionList)));" +
                             "mail;" + 
                             "subtree";
    var objRecordSet = objCommand.Execute();

    // Determine if the user object with proxyAddress exists
    if(objRecordSet.State != adStateClosed)
        retvalue = !objRecordSet.EOF;
    return retvalue;
}

/*
    String.trim - This method removes the spaces from the begining and
                  the end of the string
    Parameters:
        none
    Returns:
        The trimmed string
*/

function _String_trim()
{
    var i,j;
    for(i = 0; this.charAt(i) == " " && i < this.length; i++) {}
    for(j = this.length - 1; this.charAt(j) == " " && j >= 0; j--) {}
    return j >= i ? this.substring(i, j + 1) : "";
}
 
 
 Ezzel lehet regisztrálni:
 
 mofcomp Register.mof
 
 A Register.mof fájl:
 
#pragma namespace ("\\\\.\\root\\subscription")
#pragma autorecover
 
instance of ActiveScriptEventConsumer as $Cons
{
    Name = "IMF_Categorizer_ES";
    ScriptingEngine = "JScript";
    ScriptFileName = "C:\\Scripts\\IMF\\imfc.js";
};
 
instance of __EventFilter as $Filt
{
    Name = "IMF_Categorizer_EF";
    Query = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'CIM_DataFile' AND TargetInstance.Drive = 'c:' AND TargetInstance.Path = '\\\\SPAMDROP\\\\'";
    QueryLanguage = "WQL";
    EventNamespace = "root\\cimv2";
};
 
instance of __FilterToConsumerBinding
{
    Filter = $Filt;
    Consumer = $Cons;
};

 A mof fájlban módosítani kell az archív könyvtárat mert az jelenleg a c:\spamdrop könyvtárra mutat. Egyébként az IMF-nek meg lehet mondani a registry-ben, hogy hova tegye az archívumot itt:
HKLM\SOFTWARE\Microsoft\Exchange\ContentFilter\ArchiveDir
Kategória: Exchange | Közvetlen link a könyvjelzőhöz.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s