Artykuł dotyczy:
Spis treści:
Wstęp W poprzednim artykule przedstawiłem proces przetwarzania wiadomości przez serwer SMTP i związane z tym zdarzenia, które mogą być wykorzystywane przez komponenty COM (odbiorców zdarzeń) do rozszerzania funkcjonalności serwera SMTP. Pokazałem również, na czym polega rejestracja odbiorców zdarzeń i w jaki sposób można zarejestrować własnego odbiorcę. W tej części chciałbym przedstawić więcej informacji praktycznych, czyli przede wszystkim, jak można napisać własnego odbiorcę zdarzeń i jak go uruchomić. Aby jednak zrozumieć pewne zachowania mające wpływ na praktyczne wykorzystanie odbiorców, będziemy musieli bliżej przyjrzeć się procesowi przepływu wiadomości pocztowych przez serwer SMTP w środowisku Exchange Serwer'a. Tworzenie własnych odbiorców zdarzeń W poprzedniej części artykułu przedstawiłem wszystkie zdarzenia, jakie mogą być implementowane przez odbiorców zdarzeń. Zaliczamy do nich:
Zdarzenia protokołu SMTP:
Zdarzenia transportowe:
Zdarzenia systemowe:
Jest ich sporo i pozwalają znacznie rozszerzać i zmieniać funkcjonalność serwera SMTP. Najczęściej do tworzenia własnych rozwiązań wykorzystywane są jednak zdarzenia protokołu SMTP oraz zdarzenia transportowe OnSubmission i OnPostCategorize ze względu na w miarę łatwe możliwości ich implementacji. W artykule chciałbym pokazać proste przykłady tworzenia własnych odbiorców zdarzeń. Do tego celu z racji prostoty i czytelności najlepiej nadaje się język VBScript. Ponieważ VBScript jest językiem skryptowym, więc nie można w nim używać obiektów implementujących interfejsy binarne (podobnie jak w VisualBasic), a takie są wykorzystywane w zdarzeniach SMTP. W związku z tym odbiorca zdarzeń implementujący w języku skryptowym interfejs wymagany przez dane zdarzenie, byłby bezużyteczny, ponieważ nie miałby dostępu do żadnych danych. Z pomocą przychodzi nam tutaj biblioteka CDO, która implementuje w jednym z komponentów COM własnego odbiorcę zdarzeń OnSubmission i pozwala wykorzystać go do wykonywania kodu napisanego w VBScript. Biblioteka CDO wystawia interfejs CDO.ISMTPOnArrival (w CDO zdarzenie OnSubmission nazywane jest OnArrival), który wystarczy zaimplementować i odpowiednio zarejestrować, aby nasz "skryptowy" odbiorca zdarzeń zaczął działać. Napiszmy więc pierwszy skrypt, który zapisuje do pliku nadawcę, odbiorcę i tytuł każdej wiadomości przetwarzanej przez serwer SMTP. Oto on:
<SCRIPT LANGUAGE= "VBScript">
Const cdoRunNextSink = 0
Sub ISMTPOnArrival_OnArrival( ByVal Msg, EventStatus )
' Otwórz plik
Set fs = CreateObject("Scripting.FileSystemObject")
Set file = fs.OpenTextFile("C:\Log.txt", 8, True )
' Zapisz dane do pliku
file.Write "From: " & Msg.From & vbCrLf
file.Write "To: " & Msg.To & vbCrLf
file.Write "Subject: " & Msg.Subject & vbCrLf
EventStatus = cdoRunNextSink
End Sub
</SCRIPT>
Jak napisałem wcześniej, odbiorca musi implementować
interfejs ISMTPOnArrival wystawiany przez CDO. Jest w nim tylko jedna funkcja
OnArrival, wołana za każdym razem, gdy do warstwy transportowej zostaje
dostarczona nowa wiadomość. Wiadomość została już odebrana od zdalnego serwera
lub klienta przez warstwę protokołu serwera i właśnie będzie wstawiana do
kolejki wiadomości oczekujących na kategoryzację. Przedtem jednak serwer SMTP
daje nam szansę przetworzenia jej w naszym odbiorcy zdarzeń. Nieco dalej
zobaczymy, co trzeba zrobić, aby serwer SMTP wykonywał nasz skrypt.
Funkcja OnArrival ma dwa argumenty, pierwszy to przetwarzana wiadomość przekazana jako obiekt CDO.IMessage, drugi to wynik przetwarzania zwracany przez nasz skrypt. Możemy zwrócić wartość cdoRunNextSink (jest to wartość domyślna), co znaczy, że wiadomość ma być dalej normalnie przetwarzana przez pozostałych odbiorców zdarzeń, lub cdoSkipRemainingSinks, jeśli chcemy, aby wiadomość nie była przetwarzana przez pozostałych odbiorców. Ponieważ wartość cdoRunNextSink jest domyślna, więc równie dobrze możemy opuścić instrukcję EventStatus = cdoRunNextSink. W powyższym skrypcie otwieramy najpierw plik tekstowy a potem zapisujemy do niego adres nadawcy, odbiorcy oraz tytuł. Do pobrania tych danych wykorzystujemy pola przekazanego jako pierwszy argument obiektu CDO.IMessage - odpowiednio From, To, Subject. W ten sposób udało nam się napisać prostego odbiorcę zdarzeń ewidencjonującego wszystkie wiadomości przetwarzane przez nasz serwer. Dane te możemy wykorzystać później na przykład do tworzenia statystyk ruchu poczty i obciążenia serwera. Wypada jeszcze uzupełnić go o dodatkowe informacje, na przykład takie jak czas wysłania czy otrzymania wiadomości, liczba i rozmiar załączników. Należy tu jeszcze raz podkreślić, że funkcja OnArrival jest wołana dla każdej wiadomości dostarczanej do warstwy transportowej serwera SMTP. Zarówno dla wiadomości przychodzących jak i wychodzących, czyli tych które dostarczane są przez serwer lokalnie i tych dostarczanych zdalnie - przesyłanych do innych serwerów SMTP. Z resztą w miejscu, w którym jesteśmy - w zdarzeniu OnSubmission - nie istnieje pojęcie wiadomości przychodzącej czy wychodzącej. Po prostu serwer przetwarza pewną jeszcze nie skategoryzowaną wiadomość pocztowa i nie wie czy powinna ona zostać dostarczona do lokalnej skrzynki czy wysłana do innego serwera pocztowego. Oczywiście nic nie stoi na przeszkodzie (poza wydajnością), abyśmy sami odczytali adres nadawcy i odbiorcy i stwierdzili na podstawie danych w Active Directory, czy jest to wiadomość wysłana do lokalnego użytkownika czy nie. Tak też należy uczynić przy implementacji pewnych rozwiązań, na przykład, gdy chcemy dodawać stopkę do wszystkich maili wysyłanych w świat przez pracowników naszej firmy, ale o tym później. Przyjrzyjmy się głównym polom (właściwościom) obiektu CDO.IMessage, które możemy wykorzystać w odbiorcy zdarzeń:
Dodatkowego omówienia wymagają obiekty Fields i EnvelopeFields. Są to kolekcje pól, z których można odczytać dodatkowe informacje o wiadomości. Poprzez obiekt Fields możemy odczytać wartości wszystkich nagłówków RFC wiadomości. W tym celu należy użyć przestrzeni nazw urn:schemas:mailheader: z dodaną nazwą nagłówka. Na przykład, aby odczytać tytuł wiadomości, który zapisany jest w nagłówku Subject, należy użyć poniższego kodu:
strSubject = Msg.Fields("urn:schemas:mailheader:Subject")
W ten sposób możemy odczytywać również nagłówki
niestandardowe, np.: x-spamlevel:
strSubject = Msg.Fields("urn:schemas:mailheader:x-spamlevel")
Obiekt EnvelopeFields umożliwia nam dostęp do
danych, które nie są bezpośrednio zawarte w wiadomości, ale związane są z jej
transportem przez serwer SMTP, np. czas dostarczenia wiadomości, czy adres
klienta. Aby odczytać ich wartość należy użyć przestrzeni nazw http://schemas.microsoft.com/cdo/smtpenvelope/
Na szczególną uwagę zasługują tu pola messagestatus i recipientlist, ponieważ dzięki nim mamy wpływ na to jak wiadomość jest przetwarzana. Ustawiając odpowiednio wartość pola messagestatus możemy spowodować, że wiadomość zostanie odrzucona i nie będzie dalej przetwarzana przez serwer. Poniższy skrypt powoduje, że serwer SMTP odrzuca wszystkie wiadomości odbierane od użytkowników z domeny @firma.com. Ponieważ żądamy od serwera zaprzestania dalszego przetwarzania wiadomości, dlatego nie zostanie wygenerowany raport NDR (non delivery report) o niedostarczeniu.
<SCRIPT LANGUAGE="VBScript">
Const cdoStatAbortDelivery = 2
Const cdoSkipRemainingSinks = 1
Sub ISMTPOnArrival_OnArrival( ByVal Msg, EventStatus )
' Pobierz adres nadawcy
strSenderAddr = Msg.EnvelopeFields("http://schemas.microsoft.com/cdo/smtpenvelope/senderemailaddress")
If InStr(strSenderAddr, "@firma.com") > 0 Then
' Tych Państwa nie obsługujemy
Msg.EnvelopeFields("http://schemas.microsoft.com/cdo/smtpenvelope/messagestatus") = cdoStatAbortDelivery
' Zapisz zmiany
Msg.EnvelopeFields.Update
EventStatus = cdoSkipRemainingSinks
End If
End Sub
</SCRIPT>
Argument EventStatus ustawiamy na wartość cdoSkipRemainingSinks,
aby powiadomić dispatchera zdarzeń, że wiadomość nie musi być przetwarzana
przez pozostałych odbiorców. Nie ma takiej potrzeby skoro wiadomość i tak
zostanie przez serwer odrzucona.
Pole recipientlist używamy do sprawdzenia odbiorców wiadomości. Możemy przy jego pomocy również usunąć lub dodać dodatkowego odbiorcę. Dzięki temu możemy na przykład stworzyć odbiorcę zdarzeń, wysyłającego wszystkie wiadomości odebrane od użytkowników na dodatkowy adres SMTP. Jest to często poszukiwane rozwiązanie, bo o ile serwer Exchange pozwala określić dodatkowego odbiorcę dla wiadomości dostarczanej do skrzynki użytkownika, o tyle nie można określić dodatkowego odbiorcy dla wiadomości wysłanych przez użytkowników. Problem ten pojawia się szczególnie w przypadku zaostrzonych procedur nadzoru, gdy konieczne jest monitorowanie całego ruchu e-mail w firmie. Poniższy skrypt najpierw sprawdza czy wiadomość została wysłana przez użytkownika z naszej domeny, tu @mojafirma.com. Jeśli tak to czyta adresy obecnych odbiorców wiadomości i dodaje do nich dodatkowy adres archive@mojafirma.com. W wyniku tego każda wiadomość wysyłana przez użytkowników naszego serwera zostanie dodatkowo przesłana na wskazany adres. Ponieważ adres dodajemy do pól transportowych EnvelopeFields, to nie będzie on widoczny w treści wiadomości i użytkownik, który odbierze wiadomość nie będzie wiedział, że została ona wysłana także na adres archive@mojafirma.com.
<SCRIPT LANGUAGE="VBScript">
Sub ISMTPOnArrival_OnArrival( ByVal Msg, EventStatus )
On Error Resume Next
' Sprawdź czy mail wysyłany z naszej firmy
strFromAddr = Msg.From
If InStr( strFromAddr, "@mojafirma.com" ) > 0 Then
' Przeczytaj adresy odbiorców
Set RecipientsField = Msg.EnvelopeFields("http://schemas.microsoft.com/cdo/smtpenvelope/recipientlist")
strRecipients = RecipientsField.Value
' Dodaj dodatkowego odbiorcę
If Len(strRecipients) > 0 Then strRecipients = strRecipients & ";"
strRecipients = strRecipients & "SMTP:archive@mojafirma.com"
RecipientsField.Value = strRecipients
' Zapisz zmiany
Msg.EnvelopeFields.Update
End If
End Sub
</SCRIPT>
Jeszcze jedna ważna kwestia dotycząca wydajności. Obecnie
nasz odbiorca zdarzeń jest tworzony i ładowany przez dispatcher'a zdarzeń za
każdym razem, gdy występuje zdarzenie OnSubmission. Aby tego uniknąć możemy
zaimplementować interfejs IEventIsCacheable. Wtedy raz utworzony obiekt naszego
odbiorcy zostanie zbuforowany i będzie wykorzystywany wielokrotnie dla
obsłużenia wielu zdarzeń. Implementacja interfejsu IEventIsCacheable jest
wyjątkowo łatwa. Posiada on tylko jedną funkcję IsCacheable, która powinna
zwrócić wartość S_OK, jeśli odbiorca ma być buforowany. Nasz pierwszy przykład
z zaimplementowanym interfejsem IEventIsCacheable wyglądałby w ten sposób
<SCRIPT LANGUAGE= "VBScript">
Private Sub IEventIsCacheable_IsCacheable()
' taka implementacja w VBScript zwraca S_OK
End Sub
Sub ISMTPOnArrival_OnArrival( ByVal Msg, EventStatus )
' Otwórz plik
Set fs = CreateObject("Scripting.FileSystemObject")
Set file = fs.OpenTextFile("C:\Log.txt", 8, True )
' Zapisz dane do pliku
file.Write "From: " & Msg.From & vbCrLf
file.Write "To: " & Msg.To & vbCrLf
file.Write "Subject: " & Msg.Subject & vbCrLf
End Sub
</SCRIPT>
Rejestracja odbiorcy zdarzeń Wiemy już jak napisać prostego odbiorcę zdarzeń, zobaczmy teraz jak powiadomić serwer SMTP o jego istnieniu i spowodować, aby serwer wprowadził go do użytku. Najpierw należy zapisać kod skryptu w pliku tekstowym. Ponieważ jest on napisany w VBScript, zapiszmy go z rozszerzeniem .vbs w pliku c:\sink.vbs. Teraz użyjemy skryptu smtpreg.vbs do zarejestrowania naszego odbiorcy w następujący sposób: cscript smtpreg.vbs /add 1 OnArrival MySmtpSink CDO.SS_SMTPOnArrivalSink "MAIL FROM=*" cscript smtpreg.vbs /setprop 1 OnArrival MySmtpSink Sink ScriptName "c: \sink.vbs"Pierwsze wywołanie skryptu smtpreg.vbs rejestruje nowego odbiorcę zdarzenia OnArrival (to samo co OnSubmission, tylko pod inną nazwą) o nazwie MySmtpSink. Argument MAIL FROM=*, to filtr określający, dla jakich wiadomości uruchamiany ma być nasz odbiorca zdarzeń. W tym wypadku powinien być uruchamiany dla wszystkich wiadomości, niezależnie od tego kto jest jej nadawcą. Argument liczbowy 1 określa, dla której instancji serwera SMTP zainstalowany ma być nasz odbiorca. Jeśli w systemie jest więcej niż jedna instancja serwera to otrzymuje ona kolejny numer 2,3,4 itd. Argument CDO.SS_SMTPOnArrivalSink określa obiekt COM, który implementuje odbiorcę zdarzeń - o tym za moment. W drugim wywołaniu skryptu smtpreg.vbs ustawiamy zarejestrowanemu przed chwilą odbiorcy jeden argument o nazwie ScriptName na wartość c:\sink.vbs - czyli ścieżkę do naszego skryptu. W pierwszym wywołaniu skryptu smtpreg.vbs tak naprawdę rejestrujemy nowego odbiorcę zdarzeń, który jest implementowany przez bibliotekę CDO. Natomiast w drugim wywołaniu przekazujemy mu ścieżkę do naszego skryptu VBScript. Obiekt COM CDO.SS_SMTPOnArrivalSink implementuje odbiorcę zdarzenia OnSubmission właśnie po to, aby umożliwić uruchamianie odbiorców zdarzeń napisanych w językach skryptowych. Jak wspomniałem na wstępie, skryptowy odbiorca zdarzeń nie ma dostępu do interfejsów binarnych przekazywanych w zdarzeniach SMTP. CDO jest tu pomostem pomiędzy dispatcherem zdarzeń a naszym skryptem. Dispatcher zdarzeń serwera SMTP wysyła powiadomienie o zdarzeniu OnSubmission do odbiorcy implementowanego przez obiekt z biblioteki CDO, a ten na podstawie przekazanej mu ścieżki, wykonuje kod skryptu z odpowiedniego pliku. Po zarejestrowaniu odbiorcy musimy zrestartować usługę SMTP, aby nasz odbiorca zaczął być wywoływany. Uwaga: Zarejestrowanie odbiorcy zdarzeń z regułą filtrującą MAIL FROM=* (jak i każdą inną) spowoduje, że nie będzie on uruchamiany dla wiadomości wysyłanych przy pomocy Microsoft Outlook, OWA, CDO. Problem ten wyjaśniony jest w dalszej części artykułu. Po zarejestrowaniu odbiorcy zdarzeń możemy uruchomić ponownie skrypt smtpreg.vbs tym razem z argumentem /enum, aby poszukać wpisu dotyczącego naszej rejestracji w metabazie serwera IIS. Ma on następującą postać:
-----------
| Binding |
-----------
Event: SMTP Transport OnSubmission
ID: {C1510B94-9C43-4AAB-9B41-380D012366AE}
Name: MySmtpSink
SinkClass: CDO.SS_SMTPOnArrivalSink
Enabled: True
SourceProperties: {
Priority = 24575
}
SinkProperties: {
ScriptName = c:\sink.vbs
}
Widzimy więc, że jest to odbiorca tak naprawdę implementowany
przez obiekt CDO o nazwie klasy CDO.SS_SMTPOnArrivalSink, który w parametrach
ma zdefiniowaną ścieżkę do naszego skryptu zapisanego w pliku c:\sink.vbs,
dlatego wie jaki skrypt ma wykonywać.
Jeśli chcemy odrejestrować naszego odbiorcę zdarzeń musimy uruchomić skrypt smtpreg.vbs w z następującymi parametrami: cscript smtpreg.vbs /remove 1 onarrival MySmtpSink Ograniczenia Do tej pory wszystko przebiegało gładko. Napisaliśmy zaledwie kilka linii prostego kodu, a udało nam się zaimplementować całkiem pożyteczną funkcjonalność w naszym serwerze SMTP. Krótki skrypt wystarcza, aby kazać serwerowi SMTP odrzucać wybrane wiadomości, czy przesyłać całą korespondencję do dodatkowego wybranego odbiorcy. Niestety kolejny, jeden z najpowszechniejszych i często poszukiwanych przykładów, pokaże nam pewne problemy wiążące się z przede wszystkim skryptowymi odbiorcami zdarzeń. Jednocześnie zmusi nas do nieco dokładniejszego przyglądnięcia się procesowi przesyłania wiadomości w serwerze Exchange. Tym przykładem będzie dodawanie stopki do wiadomości wysyłanych przez użytkowników naszego serwera. Prezentuje go poniższy skrypt: <SCRIPT LANGUAGE="VBScript"> Sub ISMTPOnArrival_OnArrival( ByVal Msg, EventStatus ) On Error Resume Next ' Sprawdź czy mail wysyłany z naszej firmy strFromAddr = Msg.From If InStr( strFromAddr, "@mojafirma.com" ) > 0 Then ' Dodaj stopkę do wiadomości Msg.TextBody = Msg.TextBody & vbCrLf & "Oto nasza firmowa stopka" ' Zapisz zmiany Msg.DataSource.Save End If End Sub </SCRIPT>Jest to maksymalnie uproszczony przykład dodawania stopki do wysyłanych wiadomości. Najpierw sprawdzamy czy adres nadawcy należy do użytkownika naszego serwera, następnie dodajemy stopkę do treści wiadomości i zapisujemy zmiany. Stopka jest dodawana tylko do czysto tekstowej reprezentacji treści wiadomości. Jeśli chcemy dodawać ją również do wiadomości w formacie HTML to należy zmieniać zawartość pola HTMLBody wstawiając stopkę przed znacznikiem </body>. Gdy zarejestrujemy powyższy skrypt i rozpoczniemy testy okaże się, że nie działa on w pewnych okolicznościach. A mianowicie gdy użytkownik posiadający skrzynkę na tym serwerze wysyła wiadomość używając Microsoft Outlook, OWA czy samego CDO. W środowisku Exchange są to raczej często występujące okoliczności. Dodam tu, że w przypadku Microsoft Outlook chodzi o wysyłanie wiadomości bezpośrednio przez skrzynkę Exchange skonfigurowaną w profilu, wtedy Outlook jest dla Exchange Server'a klientem MAPI. Istnieją dwie przyczyny takiego zachowania. Pierwsza to ta, że w tym wypadku nasz skrypt, podobnie jak wszystkie poprzednie, nie zostanie w ogóle wywołany z powodu reguły, jaką zastosowaliśmy do jego rejestracji, a mianowicie MAIL FROM=*. Problem nie leży w konkretnej użytej komendzie SMTP, lecz w tym, że jeśli chcemy, aby nasze skrypty były wołane dla wiadomości wysyłanych z klientów MAPI, OWA, CDO, to nie możemy do ich rejestracji użyć żadnej reguły filtrującej - musimy zastosować pustą regułę. A to dlatego, że tego typu wiadomości nie są dostarczane do serwera SMTP przez protokół SMTP, lecz bezpośrednio do warstwy transportowej przez serwer Exchange. W związku z tym nie ma żadnej wymiany komend SMTP i dispatcher zdarzeń nie może dopasować żadnej z nich do naszej reguły filtrującej. Pozostaje nam więc użycie pustej reguły. To jednak ma z kolei negatywny wpływ na obciążenie serwera SMTP, ponieważ musi wołać nasz mocno niewydajny skrypt dla każdej przetwarzanej wiadomości pocztowej. Widzimy więc, że użycie skryptowych odbiorców zdarzeń niesie za sobą pewne poważne konsekwencje, które wymagają dokładnego rozpatrzenia w zależności od charakterystyki środowiska w jakim będą używane. Jeszcze jedna uwaga. Aby zarejestrować odbiorcę z pustą regułą należy wywołać skrypt smtpreg.vbs z następującymi parametrami: cscript smtpreg.vbs /add 1 OnArrival MySmtpSink CDO.SS_SMTPOnArrivalSink ""Skrypt smtpreg.vbs pobrany ze stron MSDN rejestruje jednak błędnie odbiorcę zdarzeń z pustą regułą, w wyniku czego nigdy nie jest on uruchamiany. Poprawnie działający skrypt do rejestracji należy pobrać z http://www.outlook.pl/downloads/smtpreg.vbs Udało nam się spowodować, że nasze przykładowe skrypty są zawsze wykonywane dla zdarzenia OnSubmission niezależnie od klienta używanego przez użytkownika do wysyłania wiadomości i teraz działają bez zarzutu. Z jednym jednak wyjątkiem. Mimo że teraz nasz skrypt jest wykonywany, to stopka wciąż nie jest dodawana do wiadomości wysyłanych przy pomocy Microsoft Outlook, OWA, CDO. Niestety jest to problem, którego nie da się rozwiązać w skryptowym odbiorcy zdarzeń. Nie można w zdarzeniu OnSubmission zmienić zawartości wiadomości pocztowej, która jest wysłana przy pomocy tych klientów. A dokładniej, nie można zmienić zawartości wiadomości, które zostały dostarczone do warstwy transportowej serwera SMTP przez komponent store driver serwera Exchange. Aby zrozumieć dlaczego tak się dzieje, musimy bliżej przyjrzeć się procesowi przetwarzania wiadomości przez serwer SMTP w środowisku Exchange Server'a. Przetwarzanie wiadomości pocztowej w środowisku Exchange Server'a W poprzedniej pierwszej części artykułu przedstawiłem przepływ wiadomości pocztowej przez serwer SMTP w przypadku, gdy wiadomość jest dostarczana i wysyłana z serwera przez warstwę (stos) protokołu SMTP. Jednak w środowisku Exchange wiadomości mogą być dostarczane do serwera również innymi drogami, co obrazuje poniższy rysunek:
Na rysunku widzimy, że protokół SMTP jest tylko jednym ze źródeł, przez które do serwera pocztowego może trafić wiadomość. Dla innych źródeł, takich jak:
Kopia wiadomości przechowywanej przez store driver serwera Exchange dociera do komponentu kategoryzatora i ten na podstawie adresu odbiorcy decyduje czy wiadomość powinna zostać dostarczona do lokalnej skrzynki na serwerze, czy powinna zostać przesłana do innego serwera. Jeśli wiadomość ma zostać przesłana dalej, to kategoryzator sprawdza, czy powędruje ona do innego serwera w organizacji, czy też powinna zostać wysłana do serwera zewnętrznego. Jest to ważna decyzja ze względu na to, że pełna treść wiadomości jest wciąż przechowywana w formacie MAPI, a w tym momencie kategoryzator musi zadecydować, w jakim formacie przesłać wiadomość dalej. Jeśli jest to serwer należący do organizacji Exchange to wiadomość może zostać przesłana w formacie MAPI. Dokładniej mówiąc jest to format TNEF (Transport Neutral Encapsulation Format), w którym do wiadomości RFC 822 dołączony jest załącznik winmail.dat zawierający spakowaną jest wiadomość MAPI. Jeśli wiadomość ma zostać przesłana do serwera zewnętrznego to musi ona cała zostać skonwertowana z formatu MAPI na format RFC 822, bo klienci serwera docelowego mogą nie mieć możliwości odczytać wiadomości MAPI. W rzeczywistości to w jakim formacie wiadomość zostanie wysłana do zewnętrznego odbiorcy zależy jeszcze od ustawień w Exchange Managerze (Global Settings | Internet MessageFormats) oraz od ustawień formatu wiadomości dla danego odbiorcy w Outlook'u. Jednak kwestia, która tutaj najbardziej nas interesuje to to, że zarówno gdy kategoryzator żąda od store driver'a stworzenia wiadomości w formacie TNEF czy też skonwertowania jej z MAPI na RFC, to store driver dokonuje tych operacji na podstawie oryginalnej wiadomości MAPI którą przechowuje, a nie na podstawie kopii, którą wysłał do warstwy transportowej serwera SMTP. W związku z tym wszelkie zmiany dokonane na treści wiadomości przez odbiorców zdarzeń zostają utracone. To powinno wyjaśnić nam, dlaczego w zdarzeniu OnSubmission nie można między innymi dodać stopki do wiadomości przesyłanej wewnątrz organizacji. Po prostu pracujemy na kopii wiadomości odrzuconej w momencie tworzenia wiadomości, która faktycznie zostanie wysłana do odbiorcy. Jednocześnie zauważmy, że pola EnvelopeFields nie są bezpośrednio związane z treścią wiadomości, lecz z procesem jej przetwarzania, dlatego zmiany dokonane np. w polu recipientlist mają wpływ na to, jak jest ona przetwarzana przez kategoryzator. Jeśli do recipientlist dodamy adres zewnętrznego odbiorcy, to kategoryzator zażąda od store driver'a skonwertowania wiadomości z formatu MAPI na RFC, aby można ją było bezpiecznie do niego wysłać. Gdy wiadomość ma zostać dostarczona do lokalnej skrzynki, która znajduje się w tym samym magazynie skrzynek (mailbox store) co skrzynka nadawcy, to kategoryzator nie wymaga od store driver'a żadnej konwersji. Wiadomość zostaje wstawiona do kolejki dostarczania lokalnego i zostaje umieszczona w odpowiedniej skrzynce. W tym wypadku również wykorzystana zostaje oryginalna wiadomość przechowywana przez store driver'a a nie jej kopia przetwarzana w warstwie transportowej. Jeśli wiadomość ma zostać dostarczona lokalnie, ale do skrzynki znajdującej się w innym magazynie skrzynek, to kategoryzator żąda dostarczenia wiadomości w formacie TNEF. Ciekawym zagadnieniem jest tutaj przypadek, gdy wiadomość ma odbiorców, do których dostarczona musi zostać w różnych formatach. Na przykład, gdy użytkownik wysyła z Outlook'a wiadomość do odbiorcy wewnętrznego z organizacji oraz do odbiorcy zewnętrznego. W takim przypadku kategoryzator żąda dostarczenia dwóch fizycznych wiadomości jednej w formacie TNEF, drugiej skonwertowanej do postaci RFC. Faktycznie dochodzi więc do fizycznego powielenia wiadomości, w terminologii technicznej proces ten nazywany jest w języku angielskim bifurcation. Jak zauważyliśmy, nie jest możliwa zmiana treści wiadomości w zdarzeniu OnSubmission, ponieważ nie pracujemy z obiektem wiadomości, który zostanie przesłany do odbiorcy. Jednak taka możliwość pojawia się w zdarzeniu OnPostCategorize, którego nie można jednak zaimplementować przy użyciu języka skryptowego typu VBScript czy Visual Basic. Tutaj jest już po kategoryzacji i nasz odbiorca ma szansę pracować na skonwertowanej wiadomości. Jednak wciąż istnieją dwa problemy. Pierwszy to przypadek, gdy wiadomość jest przesyłana do odbiorcy, który ma skrzynkę w tej samym magazynie skrzynek co nadawca. Wtedy wciąż pracujemy z kopią wiadomości, która zostanie odrzucona przez store driver'a w momencie zapisywania wiadomości w Information Store. Tu nie ma żadnego rozwiązania. Drugi problem dotyczy wiadomości skonwertowanych do formatu TNEF. Jeśli chcemy w jakiś sposób dokonać zmian na przesyłanej wiadomości to musimy pamiętać, że wiadomością tą jest tak naprawdę załącznik binarny winmail.dat. Musimy więc dokładnie znać jego format, który z tego co wiem nie jest udokumentowany. Z tego powodu ten problem jest również trudny do rozwiązania. Jednak rejestrując własnego odbiorcę dla zdarzenia OnPostCategorize sporo zyskujemy w porównaniu do zdarzenia OnSubmission. Możemy zmieniać zawartość, w tym dodawać stopkę do wszystkich wiadomości, które wysyłane są do odbiorców zewnętrznych w formacie tekstowym i HTML, a więc zazwyczaj do wszystkich odbiorców nie należących do organizacji Exchange. Zdarzenie OnPostCategorize ma jeszcze inne zalety. Ponieważ jest już po kategoryzacji, dlatego ustawione są odpowiednie pola dla obiektów nadawcy i odbiorców. Wiemy czy wiadomość jest wysyłana do odbiorcy zewnętrznego czy należącego do organizacji, nie musimy przeszukiwać Active Directory, aby to stwierdzić. Łatwo również jest rozpoznać wiadomości systemowe (na przykład replikację folderów publicznych), czy raporty NDR ponieważ mają ustawione specjalne flagi. Jeszcze raz chcę przypomnieć, że omawiane problemy nie dotyczą przypadku, gdy wiadomość zostaje dostarczona do serwera przez protokół SMTP. Wtedy zawsze mamy możliwość edycji wiadomości, ponieważ jest ona od razu w formacie RFC i jest przez cały proces przetwarzania utrzymywana przez NTFS store driver, a nie store driver serwera Exchange. W momencie gdy wiadomość ma zostać dostarczona lokalnie do skrzynki Exchange, zostaje ona przekształcona przez store driver Exchange do formatu MAPI, aby mogła być odczytana przez użytkowników Outlook'a. Przekształcenie to jednak odbywa się na wiadomości RFC, która niesie wszystkie zmiany wprowadzone w treści przez odbiorców zdarzeń. Wnioski Podsumowując nasze dość długie rozważania nad tematem dodawania stopki do wiadomości (ogólnie edycji) i sposobu działania odbiorców zdarzeń, dochodzimy do następujących wniosków:
Podsumowanie Przy pomocy skryptów VB można często w bardzo prosty sposób zrealizować zadania, które są niedostępne przy użyciu standardowych ustawień serwera pocztowego, takich jak dodawanie stopki, czy przesyłanie wychodzących wiadomości do dodatkowego odbiorcy. Mogłoby także wydawać się, że napisanie prostego skryptu może zaoszczędzić nam sporo wydatków na zakup drogiego oprogramowania implementującego zbliżoną funkcjonalność. Osobiście przestrzegam jednak przed wykorzystywaniem odbiorców zdarzeń napisanych w językach skryptowych w środowiskach produkcyjnych i zalecam dokładne ich przetestowanie przed podjęciem takiej decyzji. Skrypty są napisane w językach wysokiego poziomu i ich wykonanie pociąga za sobą duży nakład zasobów pamięci i procesora, zazwyczaj o wiele większy niż w analogicznych programach mających postać kodu maszynowego i napisanych w takich językach jak C\C++. Dlatego warto najpierw porównać na serwerze testowym, jaki wpływ na jego obciążenie ma zainstalowanie skryptu odbiorcy zdarzeń. Na pewno pozytywny wpływ na wydajność będzie miało zainstalowanie odbiorcy z odpowiednią regułą, jak najbardziej ograniczającą liczbę przetwarzanych przez niego wiadomości, to jednak z kolei powoduje, że nasz odbiorca zdarzeń nie będzie w ogóle wywoływany dla wiadomości, które są dostarczane do warstwy transportowej serwera inną drogą niż przez protokół SMTP. Według mnie możliwość tworzenia własnych skryptowych odbiorców zdarzeń jest raczej pokazaniem tego, co można zrobić przy ich wykorzystaniu i jakie są możliwości rozszerzania serwera SMTP, niż zachętą do ich stosowania w środowiskach produkcyjnym. Taki też był cel tego artykułu. Oczywiście można wyobrazić sobie sytuacje, gdy realne jest użycie skryptowych odbiorców zdarzeń, zwłaszcza w środowiskach, które nie charakteryzują się dużym ruchem poczty. Z pewnością nie można odmówić im kilku podstawowych zalet: prostoty, łatwości i niskiej ceny wykonania. Ponieważ od dawna zajmuję się tematyką Exchange Server'a i firmie, którą prowadzę, zdarzyło się stworzyć niejeden projekt wykorzystujący odbiorców zdarzeń serwera SMTP, dlatego postaram się w najbliższym czasie udostępnić program, który implementował będzie opisane tu przykłady. Program napisany zostanie przy użyciu języka niskiego poziomu C++, dlatego powinien być znacznie wydajniejszy od skryptów VB. Będzie również odbiorcą zdarzenia OnPostCategorize, dzięki czemu będzie mógł dodawać stopkę do wszystkich wiadomości wysyłanych na zewnątrz organizacji Exchange niezależnie od klienta pocztowego użytego przez nadawcę. Może w przyszłości uda się dodawać także inne funkcjonalności do programu, na przykład powiadomienia SMS o przychodzących wiadomościach. Dodatkowa literatura CDO for Windows 2000 SMTP Transport Architecture Jeśli masz jakieś pytania lub komentarze dotyczące tego artykułu, napisz na forum. (c) CodeTwo Wszelkie prawa zastrzeżone. Artykuł ten, ani żadna z jego części nie może być kopiowana i/lub publikowana bez wyraźnej zgody autora. |