Как стать автором
Обновить

Пишем viewer почтовой базы MS Exchange (часть 2)

Время на прочтение13 мин
Количество просмотров2.8K

Здравствуйте, читатели Хабрахабр!

Это завершение поста начатого вот здесь.

В принципе, мы почти все уже сделали, осталось небольшое окончание. Напомню, мы взяли EDB базу, открыли ее, воспользовавшись технологией ESE, перечислили имеющиеся таблицы и начали перечислять колонки внутри этих таблиц. Нам осталось дополучить колонки, получить значения столбцов и вуаля, база прочитана.

В предыдущем посте я не дописал функцию отвечающую за переход на следующую колонку в таблице, а также описание структуры SColumnInfo, вот они:
typedef struct tagColumnInfo
{
        DWORD dwId;
        std::wstring sName;
        DWORD dwType;
        DWORD dwMaxSize;
}SColumnInfo;

bool CJetDBReaderCore::TableEnd ( std::wstring sTableName )
{
    std::map <std::wstring, JET_TABLEID>::const_iterator iter = m_tables.find ( sTableName );
    if ( iter != m_tables.end() )
    {
        if ( _JetMove ( m_sesid, iter->second, JET_MoveNext, 0 ) )
        {
            return true;
        }
        else return false; 
    }
 
    return true;
}

Соответственно когда перейти на следующий элемент не удается, то это признак, что таблица закончилась.

Мы получили список колонок и занесли их в наши структуры, осталось получить значения.

Получаем значения ячеек в колонках

Пишем следующую функцию:
JET_ERR CJetDBReaderCore::EnumColumnsValues ( 
    SDBTableInfo &sTableInfo, 
    std::list<SColumnInfo> &sColumnsInfo )
{
    typedef std::basic_string<byte> CByteArray;
    JET_ERR jRes            = OpenTable ( sTableInfo.sTableName );
    if ( jRes == JET_errSuccess )
    {
        JET_COLUMNLIST sColumnInfo;
        jRes = GetTableColumnInfo ( sTableInfo.sTableName&sColumnInfo, FALSE );
        if ( jRes != JET_errSuccess ) return jRes;
 
        if ( !MoveToFirst ( sTableInfo.sTableName ) )
        {
            std::vector<CByteArray> Holders(sColumnsInfo.size());
            std::vector<CByteArray>::iterator HolderIt = Holders.begin();
 
            std::vector<JET_RETRIEVECOLUMN> Row ( sTableInfo.sColumnInfo.size() );
            std::vector<JET_RETRIEVECOLUMN>::iterator It = Row.begin();
 
            std::list<SColumnInfo>::const_iterator ColumnInfoIt 
                = sColumnsInfo.begin();
 
            for ( int nColNum = 0; ColumnInfoIt != sColumnsInfo.end(); 
                ++ColumnInfoIt, ++It, ++HolderIt, ++nColNum )
            {
                DWORD dwMaxSize = ColumnInfoIt->dwMaxSize ? ColumnInfoIt->dwMaxSize : 
MAX_BUFFER_SIZE;
                It->columnid = ColumnInfoIt->dwId;
                It->cbData = dwMaxSize;
                HolderIt->assign(dwMaxSize, '\0');
                It->pvData = const_cast<byte*>(HolderIt->data());
                It->itagSequence = 1;
            }
 
            do
            {
                GetColumns ( 
                    sTableInfo.sTableName&Row[0]
                    static_cast <INT> ( Row.size() ) );
 
                ColumnInfoIt = sColumnsInfo.begin();
                int iSubItem = 0;
                for ( It = Row.begin(); It != Row.end(); ++It, 
                    ++ColumnInfoIt, ++iSubItem )
                {
                    if((*It).cbActual)
                    {
                        std::wstring s;
                        std::string tmp;
                        wchar_t buff[16];
 
                        switch(ColumnInfoIt->dwType)
                        {
                        case JET_coltypBit:
                            s = *reinterpret_cast<byte*>((*It).pvData) ? L"True" : L"False";
                            break;
                        case JET_coltypText:
                            tmp.assign(reinterpret_cast<char*>((*It).pvData)(*It).cbActual);
                            s.assign(tmp.begin(), tmp.end());
                            break;
                        case JET_coltypLongText:
                            s.assign(reinterpret_cast<wchar_t*>((*It).pvData)
                                (*It).cbActual / sizeof ( wchar_t ) );
                            break;
                        case JET_coltypUnsignedByte:
                            s = _ltow(*static_cast<byte*>((*It).pvData), buff, 10);
                            break;
                        case JET_coltypShort:
                            s = _ltow(*static_cast<short*>((*It).pvData), buff, 10);
                            break;
                        case JET_coltypLong:
                            s = _ltow(*static_cast<long*>((*It).pvData), buff, 10);
                            break;
                        case JET_coltypGUID:
                            wchar_t wszGuid[64];
                            ::StringFromGUID2(*reinterpret_cast<const GUID*>((*It).pvData)
                                wszGuid, sizeof(wszGuid));
                            USES_CONVERSION;
                            s = wszGuid;
                            break;
                        }
 
                        sTableInfo.sColumnInfo[iSubItem].sColumnValues.push_back ( s );
                    }
 
                    else sTableInfo.sColumnInfo[iSubItem].sColumnValues.push_back ( L"<empty>" );
 
                }
            }
            while ( !TableEnd ( sTableInfo.sTableName ) );
        }
 
        jRes = CloseTable ( sTableInfo.sTableName );
    }
 
    return jRes;
}

Мы пройдемся по всем строчкам и для каждой вычитаем значение ячеек. И начнем с того, что переместимся на первый элемент, установив курсор в соответствующую позицию (функция MoveToFirst, см. предыдущий пост).

Все очень похоже на то, как мы перечисляли имена таблиц из MSysObjects, за одним исключением: тогда мы знали, что данные имеют строковый тип, а здесь данные могут быть произвольными. Поэтому мы создадим специальные byte’вые контейнеры Holders куда и сохраним будущую информацию, а так как это обычный вектор, то нам не придется думать об очистки памяти, она сама уничтожится когда мы выйдем из зоны видимости этой функции.

Т.е.:
  • Для краткости объявим:
    typedef std::basic_string<byte> CByteArray
  • Создадим Holder'ы для данных, в них и попадет информация из базы:
    std::vector<CByteArray> Holders(sColumnsInfo.size());
  • А далее в цикле увяжем куски памяти куда будут возвращаться данные и наши холдеры, а для этого сначала выделим под них память, достаточную для удержания всей информации (максимальный размер данных мы получили при перечислении колонок):
    HolderIt->assign(dwMaxSize, '\0');
    It->pvData = const_cast<byte*>(HolderIt->data());
  • А теперь в цикле вернем информацию для каждой колонки в наш вектор JET_RETRIEVECOLUMN, который использует память выделенную для холдеров:
     GetColumns(sTableInfo.sTableName&Row[0],static_cast <INT> ( Row.size() ) );


В итоге мы вычитаем одну строчку из таблицы. Но т.к. данные это массив байт, его нужно преобразовать во что-то более читаемое, поэтому мы попытаемся распарсим ячейки в зависимости от их типов. В данном примере разбираются следующие типы (как наиболее очевидные):
  • JET_coltypBit
  • JET_coltypText
  • JET_coltypLongText
  • JET_coltypUnsignedByte
  • JET_coltypShort
  • JET_coltypLong
  • JET_coltypGUID

Описание имеющихся в ESE типов можно найти здесь.

Заключение


Вот мы и написали viewer почтовой базы! Если начать копаться в этих бесконечных таблицах можно найти много интересного, например, что существенного изменилось в Exchange 2010 по сравнению со всеми предыдущими версиями и многое-многое другое.

У того, что мы написали есть существенный недостаток: наглядность. Мы заполнили кучу структур, которые никак не отобразили. Т.е. нужно написать GUI программу, которая в наглядном виде покажет содержимое всех этих структур. Написать ее должно быть не сложно, т.к. представлять она из себя будет набор простых гридов, благо структуры мы уже подготовили и заполнили, т.е., грубо говоря, осталось только распечатать. Пару примеров таких таблиц я приведу ниже.

Что нам это дало практически? С одной стороны, немногое, т.к. большая часть информации находится в бинарном виде JET_coltypBinary и описание этого форматы вы нигде не найдете. Здесь без реверсинженеринга работы Exchange уже не обойтись. Но с другой стороны мы можем лучше понять «А как оно работает на самом деле?», а это может оказаться бесценной информацией. Плюс это полезный материал для «старта», если придется чем-то подобным заниматься в будущем.

Где это может пригодиться? Это может пригодится только в тех случаях когда другое API не работает или же не удовлетворяемый каким-то требованиям, например, требованию производительности. Примерами таких задач могут быть: антивирусы, системы аудита, миграции и восстановления.

Напоследок два небольших примера полученных данных.
Кусочек списка таблиц в базе:


Список папок и их типов:


Спасибо, что прочитали и уделили ваше время!

P.S. Данный код является адаптированной и уменьшенной версией для поста. Поэтому в коде есть некоторые недоработки, а точнее затычки на местах урезанного функционала. Пожалуйста, не обращайте на них внимания это не production, а я хотел показать рабочие примеры. Код полностью рабочий и написан так, чтобы его можно было разместить в интернете и при этом не «съесть» все место на странице. Спасибо за понимание.

P.P.S. Я понимаю, что ввиду специфики данная информация вряд ли окажется полезной для широкого круга лиц, но если она поможет, даже одному человеку, я буду рад, и время, потраченное на этот пост, окупится.

Ссылки по теме
  1. Extensible Storage Engine на MSDN
  2. Статья на русском языке (чуть ли не единственная в рунете статья про использование ESE API)
  3. Примеры использования функций ESE
Теги:
Хабы:
Всего голосов 21: ↑16 и ↓5+11
Комментарии3

Публикации