MAPILab
EnglishDeutschRussian
Вы здесь: Главная / Техническая поддержка / Cтатьи / Как избежать срабатываний системы б...

Как избежать срабатываний системы безопасности при работе программ для Microsoft® Outlook® на Visual Basic

Данная статья описывает, как обойти срабатывания системы безопасности при работе программ для Microsoft® Outlook® на Visual Basic, используя подсистему MAPI и без использования третьесторонних библиотек. Обзорно рассказывает о существующих третьесторонних библиотеках для работы с MAPI. Статья предназначена для опытных программистов на Visual Basic 5.x/6.x и Visual Basic .NET.

Напоминание системы безопасности Outlook Последние версии Microsoft Outlook, включая Microsoft Outlook 2003, имеют защиту от несанкционированного доступа к адресной книге и базе почтовых сообщений, а также защиту от несанкционированной отправки электронных писем через службы Outlook вредоносными программами. На практике работа защиты выражается в показываемых пользователю предупреждениях системы безопасности при обращении программы к некоторым объектам и свойствам объектов Outlook.

Система безопасности имеет один неприятный побочный эффект: программисты на Visual Basic не имеют способа доказать Microsoft Outlook благонадежность своих программ, который бы работал со всеми версиями и конфигурациями Outlook.

Первые версии Microsoft Outlook не содержали подобной системы безопасности, однако для всех версий начиная с Outlook 98 были выпущены обновления системы безопасности. Дополнительную информацию по обновлениям и список “небезопасных” объектов и свойств можно найти в следующих документах Microsoft:

Полный список документов, описывающих изменения системы безопасности после установки сервис паков или обновлений, а также в различных версиях Microsoft Outlook, несколько больше представленного выше, все их можно найти на сайте Microsoft.

Авторы вредоносных программ пытаются не только использовать отправку писем через Outlook для отсылки вирусов, но также пытаются сканировать адресные книги и базы почтовых сообщений для поиска новых жертв и маскировки своих писем под письма инфицированного пользователя. Чем шире начинают использоваться различные приемы в вредоносных программах, тем жестче становится система безопасности Outlook. В Outlook 2003 система безопасности срабатывает уже не только при попытке получить адрес отправителя письма, но даже при попытке получить его текст.

Экскурс в разработку приложений для Microsoft Outlook

Microsoft Outlook предоставляет программистам три набора интерфейсов:

  • объектная модель Outlook (которую мы будем в дальнейшем называть OOM), имеющая как средства работы с хранилищами данных, так и позволяющая автоматизировать и управлять Outlook, например, добавлять свои элементы управления в меню и на панели управления Outlook
  • CDO (Collaboration Data Objects) – высокоуровневый набор интерфейсов, позволяющий работать с системой электронной почты Outlook и в реализации для Microsoft Outlook являющаяся расширением над системой MAPI
  • MAPI (Messaging Application Programming Interface) – предложенный Microsoft в начале 1990-x годов открытый интерфейс для систем электронной почты, также известный под названием Extended MAPI (для избежания путаницы с набором из 12 функций именуемом Simple MAPI).

Система безопасности Outlook закрывает методы OOM и CDO и не затрагивает методы MAPI, так как интерфейсы MAPI не доступны из скриптовых языков программирования (таких как Jscript и VBScript) и Visual Basic.

CDO, OOM и MAPI при работе с хранилищем данных Outlook оперируют одними и теми же наборами данных представленных различными объектами. Так, например, письмо электронной почты в CDO представлено объектом типа Message, в OOM - _MailItem, а в MAPI – объектом реализующим интерфейс IMessage. Свойства этих объектов, хотя и назваются по-разному, предоставляют доступ к одним и тем же данным. Так, адрес электронной почты отправителя письма доступен в CDO через Message.Sender.Address, в OOM – через свойство SenderEmailAddress объекта _MailItem, а в MAPI – через свойство PR_SENDER_EMAIL_ADDRESS объекта, реализующего интерфейс IMessage.

И если обращение к свойствам Message.Sender.Address или _MailItem.SenderEmailAddress вызывает срабатывание системы безопасности, то обращение к свойству PR_SENDER_EMAIL_ADDRESS объекта MAPI такого срабатывания не вызывает.

К счастью для программистов на Visual Basic, и в объектах CDO, и в объектах OOM есть свойство MAPIOBJECT, содержащее ссылку на интерфейс соответствующего данному объекту интерфейса MAPI. Свойство MAPIOBJECT появилось в OOM и в CDO начиная с Microsoft Outlook ’98. В статье Microsoft под номером 296483 говорится, однако, что свойство MAPIOBJECT объектов OOM присутствует только для совместимости с CDO, и это свойство является “hidden property of Outlook Object Model objects, and is not meant to be used from the Outlook object”. Но нам не известно о практических ограничениях использования этого свойства в OOM за исключением тех, о которых говорится в данной статье.

Итак, спасительные интерфейсы MAPI совсем рядом – как же ими можно воспользоваться?

Введение в MAPI

Мы не будем пытаться описывать в этом разделе архитектуру MAPI или особенности ее реализаций (вы можете найти это во многих других источниках, в том числе в MSDN), а сосредоточимся на практических приемах использования MAPI в контексте данной статьи.

Для практического изучения MAPI мы воспользуемся условно-бесплатным плагином OutlookSpy, который можно загрузить с сайта www.dimastr.com. Установите плагин, и вы получите великолепную возможность исследовать как объектную модель Outlook, так и CDO.

IMessage Properties

Откройте какое-нибудь письмо, нажмите на панели инструментов OutlookSpy кнопку CurrentItem и перед вами предстанет объект _MailItem, представляющий письмо в OOM. Найдите свойство MAPIOBJECT, щелкните по нему и нажмите кнопку “Browse” – и OutlookSpy покажет Вам интерфейс IMessage соответствующего объекта MAPI.

Интерфейс IMessage реализует следующие методы:

GetAttachmentTable Returns the message's attachment table.
OpenAttach Opens an attachment.
CreateAttach Creates a new attachment.
DeleteAttach Deletes an attachment.
GetRecipientTable Returns the message's recipient table.
ModifyRecipients Adds, deletes, or modifies message recipients.
SubmitMessage Saves all changes to the message and marks it as ready for sending.
SetReadFlag Sets or clears the MSGFLAG_READ flag in the PR_MESSAGE_FLAGS property of the message and manages the sending of read reports.

Фактически, для нас письмо в MAPI представляется таблицей вложений, таблицей получателей и наборов свойств. Список свойств объекта можно получить вызовом IMAPIProp::GetPropList, а сами свойства – IMAPIProp::GetProps. То, что мы видем на закладке “GetProps” в OutlookSpy и предствляет собой результат работы этих двух функций.

Давайте рассмотрим поближе свойство PR_SENDER_EMAIL_ADDRESS, которое уже упоминалось выше. Тэг PR_SENDER_EMAIL_ADDRESS соответствует шестнадцатеричному числу &H0C1F001E, в котором младшая часть &H001E – это тип свойства PT_STRING8, означающей обычную ANSI-строку. С Visual Studio поставляются файлы MAPIDEFS.H и MAPITAGS.H, в первом находятся объявления типов свойств, а во втором – самих свойств. Эти файлы также включены в Microsoft Platform SDK.

Однако, не все свойства можно получить вызовом IMAPIProp::GetProps. Значения таких свойств как PR_BODY (тело письма) могут быть достаточно большими, и при вызове функции будет возвращен код ошибки MAPI_E_NOT_ENOUGH_MEMORY. Для получения значений таких свойств используется метод IMAPIProp::OpenProperty, который открывает свойство как поток (IStream).

Есть также свойства содержащие значения в компрессированном виде, например PR_RTF_COMPRESSED, содержащее компрессированное тело письма в формате RTF. При чтении такого свойства при помощи IMAPIProp::OpenProperty, значение будет загружено “как есть”. Для получения данных непосредственно в формате RTF, нужно для чтения потока воспользоваться функцией MAPI WrapCompressedRTFStream.

Итак, о свойствах объектов мы теперь знаем достаточно, чтобы получить адрес электронной почты отправителя или тело письма без срабатывания системы безопасности. Если, кончено, научимся работать с MAPI из Visual Basic.

Свойства MAPI объекта доступны для программистов на Visual Basic посредством CDO через коллекцию Fields. Поле ID объекта Field представляет собой не что иное, как тэг MAPI-свойства. Вы можете в OutlookSpy, добравшись до письма через CDO, перейти на закладку Script и выполнить следующий код на JScript:

for (a = 1; a <= Fields.Count; a++) { Debug.Print(a + " " + Fields.Item(a).ID + " " + Fields.Item(a).Name); Debug.Print(Fields.Item(a).Value); Debug.Print("=================================="); }

Этот код распечатает все свойства объекта, которые Вы видели изучая интерфейс IMessage и которые можно получить через IMAPIProp::GetProps. Однако во время исполнения этого кода Вы опять столкнетесь со срабатыванием системы безопасности.

Работа с MAPI из Visual Basic без третьесторонних библиотек

Несмотря на широкораспространенное мнение, что из Visual Basic никак нельзя работать с MAPI, мы рискнем его опровергнуть. Конечно, до всех сокровищ MAPI из Visual Basic добраться практически невозможно, но задача получения свойств объекта не должна представлять большой сложности для опытного программиста труда.

В MAPI есть следующая функция:

HrGetOneProp(
LPMAPIPROP pmp,
ULONG ulPropTag,
LPSPropValue FAR * ppprop
);

Которая принимает указатель на интерфейс объекта (хранящийся в MAPIOBJECT), тэг, и возвращает указатель на значение. Самое сложное здесь – это работа с возвращаемым значением. В MAPI SDK оно описано следующим образом:

typedef struct _SPropValue
{
ULONG ulPropTag;
ULONG dwAlignPad;
union _PV Value;
} SPropValue, FAR *LPSPropValue;

В ulPropTag сохраняется тэг запрошенного значения если вызов произошел без ошибок, а объединение Value описано в MAPIDEFS.H следующим образом:

typedef union _PV { short int i; /* case PT_I2 */ LONG l; /* case PT_LONG */ ULONG ul; /* alias for PT_LONG */ float flt; /* case PT_R4 */ double dbl; /* case PT_DOUBLE */ unsigned short int b; /* case PT_BOOLEAN */ CURRENCY cur; /* case PT_CURRENCY */ double at; /* case PT_APPTIME */ FILETIME ft; /* case PT_SYSTIME */ LPSTR lpszA; /* case PT_STRING8 */ SBinary bin; /* case PT_BINARY */ LPWSTR lpszW; /* case PT_UNICODE */ LPGUID lpguid; /* case PT_CLSID */ LARGE_INTEGER li; /* case PT_I8 */ SShortArray MVi; /* case PT_MV_I2 */ SLongArray MVl; /* case PT_MV_LONG */ SRealArray MVflt; /* case PT_MV_R4 */ SDoubleArray MVdbl; /* case PT_MV_DOUBLE */ SCurrencyArray MVcur; /* case PT_MV_CURRENCY */ SAppTimeArray MVat; /* case PT_MV_APPTIME */ SDateTimeArray MVft; /* case PT_MV_SYSTIME */ SBinaryArray MVbin; /* case PT_MV_BINARY */ SLPSTRArray MVszA; /* case PT_MV_STRING8 */ SWStringArray MVszW; /* case PT_MV_UNICODE */ SGuidArray MVguid; /* case PT_MV_CLSID */ SLargeIntegerArray MVli; /* case PT_MV_I8 */ SCODE err; /* case PT_ERROR */ LONG x; /* case PT_NULL, PT_OBJECT (no usable value) */ } __UPV;

Приведенный ниже пример писался и отлаживался на Visual Basic 6, и чтобы сократить пример, мы описали эту структуру попроще:

Private Type SPropValue ulPropTag As Long dwAlignPad As Long val1 As Long val2 As Long val3 As Long End Type

Итак, вызов HrGetOneProp создает эту структуру и возвращает нам указатель на нее. После окончания работы со структурой мы должны уничтожить ее, передав полученный указатель функции MAPIFreeBuffer:

Private Declare Function HrGetOneProp Lib "mapi32" _ Alias "HrGetOneProp@12" ( _ ByVal lpMapiProp As IUnknown, _ ByVal ulPropTag As Long, _ ByRef lppProp As Long) As Long Private Declare Function MAPIFreeBuffer Lib "mapi32" ( _ ByVal lppProp As Long) As Long

Надо обратить также внимание, что если запрашиваемые строковые свойства возвращаются как ANSI-строки, и для их использования в Visual Basic нужно преобразовать из в Unicode. Нижеследующий код был заимствован из шестой главы книги “Win32 API Programming with Visual Basic” написанной Steven Roman:

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _ Destination As Any, Source As Any, ByVal Length As Long) Private Declare Function lstrlenA Lib "kernel32.dll" ( _ ByVal lpString As Long) As Long Private Function LPSTRtoBSTR(ByVal lpsz As Long) As String Dim cChars As Long cChars = lstrlenA(lpsz) LPSTRtoBSTR = String$(cChars, 0) CopyMemory ByVal StrPtr(LPSTRtoBSTR), ByVal lpsz, cChars LPSTRtoBSTR = Trim(StrConv(LPSTRtoBSTR, vbUnicode)) End Function

Итак, теперь у нас есть все необходимое, чтобы получить значение полюбившегося нам тэга PR_SENDER_EMAIL_ADDRESS:

Private Sub PrintEmail() Dim objSesson As Object Dim objItem As Object Set objSession = CreateObject("MAPI.Session") objSession.Logon Set objItem = objSession.Inbox.Messages.GetFirst Dim ptrSProp As Long ptrSProp = 0 If HrGetOneProp(objItem.MAPIOBJECT, &H0C1F001E, ptrSProp) = 0 Then Dim sprop As SPropValue CopyMemory sprop, ByVal ptrSProp, 20 MsgBox LPSTRtoBSTR(sprop.val1) MAPIFreeBuffer ptrSProp End If Set objItem = Nothing Set objSession = Nothing End Sub

Процедура PrintEmail делает следующее:

  1. Создает сессию CDO
  2. Вызовом Logon осуществляет инициализацию MAPI (если Вы используете не CDO, а OOM, вам может потребоваться сделать это вызовом функции MAPIInitialize)
  3. Получает в objItem первое письмо в папке Inbox в Outlook
  4. Вызовом HrGetOneProp получает указатель на структуру SPropValue в переменную ptrSProp
  5. Копирует данные из этой структуры в переменную spprop, после чего указатель на строку-результат (в формате ANSI) появляется в spprop.val1
  6. Переводит вызовом LPSTRtoBSTR строку в формат Visual Basic и выводит ее на экран вызовом MsgBox
  7. Вызовом MAPIFreeBuffer освобождает память, выделенную под структуру вызовом HrGetOneProp
  8. Освобождает объект и закрывает сессию CDO

Итак, поздравляем! У нас получилось обойти систему безопасности без использования сторонних библиотек и компонентов. А что могут предложить нам компоненты?

Компоненты для работы с MAPI

На самом деле, в этом разделе стоило бы рассказать всего об одном компоненте – Redemption Дмитрия Стеблеченко (www.dimastr.com). Других компонентов на сегодняшний день не существует, и даже великий SlipStick не содержит иных ссылок кроме как на Redemption:

  • http://www.slipstick.com/outlook/esecup.htm
  • Но по просьбе одного из наших друзей мы сделали ему маленький компонент для легко чтения свойств MAPI из Visual Basic. Компонент MAPIProp занимает на диске всего 25 Кб, и распространяется бесплатно вместе с исходными кодами на Visual C++ 6.0 и примерами на JScript, VBScript, Visual Basic 6 и Visual Basic .NET. Загрузить компонент можно на этой странице:

  • http://www.mapilab.com/ru/dev/mapiprop/
  • Компонент помимо функций инициализации MAPI содержит методы для чтения свойств объекта, чтения больших по объему значений свойств (типа PR_BODY) и прозрачного чтения компрессированных свойств (типа PR_RTF_COMPRESSED). Компонент возвращает значения свойств в “родном” для Visual Basic виде, и вам не придется задумываться о преобразовании ANSI-строк в VB-строку.

    Однако, что делать, если нужно получить, например, список получателей письма? Этот список нельзя получить через свойства IMessage, так как он представлен отдельной таблицей. Можно попробовать воспользоваться функцией HrGetRecipientList, аналогично тому как это было описано выше, но работа со сложными структурами содержащими указатели является не самой сильной строной Visual Basic. Разумным выходом будет библиотека Redemption.

    Эта библиотека, как и OutlookSpy, является условно-бесплатной, но ее возможности многократно перекрывают возможности нашей библиотеки, если их вообще хоть как-то можно сравнивать.

    Redemption представляет альтернативные реализации всех объектов Outlook, свойства которых блокируются системой безопасности. Так, для MailItem в Redemption есть SafeMailItem (чьи методы не блокируются системой безопасности), для ContactItem есть SafeContactItem и т.д. Помимо этого, Redemption имеет специальный объект MAPIUtils, содержащий более десятка методов для работы с MAPI, включая и методы для чтения и записи свойств MAPI.

    История Redemption насчитывает несколько лет, и он, как и OutlookSpy, является “must have” продуктом для всех, кто собирается создавать серьезные приложения для Microsoft Outlook на Visual Basic.

    Модуль поддержки
    Оставить предложение
    Cтатьи
    Часто задаваемые вопросы
    Поиск на MAPILab.com:
    Подпишитесь на нашу рассылку:
    E-Mail:
    Быстрый переход к разделам:

    MAPILab Reports

    Microsoft Outlook

    Outlook Express

    Microsoft Office


    Продукты для SharePoint

    Exchange Server

    Cовместная работа

    SharePoint Workflow