Сообщений 0    Оценка 0        Оценить  
Система Orphus

Подключаемся к серверу Novell NetWare (4.x, 3.x)

Автор: Игорь Вартанов

Версия текста: 1.0

Демонстрационный проект (12.3 кбайт)

НЕОБХОДИМОЕ ЗАМЕЧАНИЕ Автор осведомлен о том, что уже существуют, и довольно длительное время, серверы версии 5.x, но, во-первых, он не имеет возможности познакомиться с ними ввиду отсутствия доступа к ним, и во-вторых, в силу указанных причин автор не имеет возможность программировать под указанные версии NetWare. Приведенные ниже методики опробованы на серверах NetWare 4.x, хотя с большой степенью вероятности они будут работать и на более новых версиях. Все, что касается серверов версии 3.x, было опробовано на тех же серверах NetWare 4.x, работающих в режиме эмуляции BINDERY. Причина та же - отсутствие доступных серверов 3-й версии. Во всех случаях предполагается, что код будет работать на рабочей станции, на которой установлен сетевой клиент Novell NetWare Client32.

Введение

Итак, админ сети, для которой вам предстоит написать приложение (или крутой программный комплекс), еще до вашего там появления остановил свой выбор на файл-серверах Novell NetWare, изменить уже ничего нельзя, и в вам необходимо воспользоваться файлами, находящимися на этих серверах. Тогда первая задача, которую вам предстоит решить - это...

Присоединение к серверу NetWare 4.x (3.x)

Если вы думаете, что это сложно - что ж, возможно вы ошибаетесь. Присоединение к серверу NetWare выполняется функцией NWAttachToFileServer(), которая имеет следующий прототип:

NWCCODE NWAttachToFileServer(

   pnstr8            serverName,
   nuint16           scopeFlag,   // должно быть равно 0
   NWCONN_HANDLE *   newConnID );
В случае успеха функция возвратит 0 (SUCCESSFUL) (хотя не смертельным будет и результат 0x8800 (ALREADY_ATTACHED), что говорит нам о том, что мы уже имеем соединение с данным сервером).

Необходимо сразу оговориться, что рассмотрение вопроса мы будем вести в рамках клиентских приложений для платформы Win32. Хэдеры, поставляемые в составе Novell NetWare SDK (более позднее название - NDK /NetWare Developer Kit/) рассчитаны на применение для нескольких платформ (DOS, Windows, Windows9x, WindowsNT, OS/2) и имеют платформонезависимый синтаксис. Для нас с вами будет важно, что возвращаемый тип (практически всех функций API) NWCCODE (NetWare Completion CODE) есть DWORD, pnstr8 есть указатель на строку беззнакового 8-битового типа, т.е. BYTE, и т.д. Таблица соответствия типов дает общее представление о типах, используемых в NetWare.

Итак, чтобы присоединиться к серверу, нужно всего лишь указать его имя и получить хэндл соединения, через который впоследствии можно будет идентифицировать сервер с указанным именем.

Свершилось. Мы присоединились к серверу NetWare. Что нам дает это присоединение? Не слишком много ценной информации - фактически, мы получили анонимный вход на сервер, при помощи которого можно узнать разнообразную информацию, на которую сервер не накладывает ограничений, связанных с безопасностью - это текущее время сервера, имена томов на нем, информация о зарегистрированных пользователях и группах сервера, и т.п. Но главное, ради чего мы начали эту беседу - файловые операции, - нам пока недоступно. И недоступно по той простой причине, что на файловые операции сервер накладывает ограничения - их разрешено выполнять пользователю, зарегистрированному на данном сервере (в данном дереве, но о дереьях - позже), и прошедшему идентификационную проверку подлинности. Проще говоря, нам необходима...

Регистрация пользователя

Сервер NetWare 3.x

Надо отметить, что серверы 3-ей версии показали удивительную живучесть и неприхотливость. Отчасти это объясняется тем, что они успешно выполнялись (и, надо сказать, выполняются) на процессорах начиная от i386 и выше, и при минимальных требованиях к железу обеспечивают вполне приемлемое качество сервиса. Кроме того, Microsoft в свое время вынуждена была ввести в свои операционные системы поддержку сетевого клиента серверов NetWare 3.x-4.x, чтобы удовлетворить запросы потребителей. Для работы в сетях серверов NetWare 3.x написана масса программных продуктов. В любом случае необходимость (и возможность) подключения к серверам версий 3.x актуальна и на сегодняшний день.

Регистрация пользователя сервера NetWare 3.x производится вызовом функции NWLoginToFileServer() (в SDK старых версий называвшейся LoginToFileServer()), имеющей прототип:

NWCCODE NWLoginToFileServer(

   NWCONN_HANDLE   conn,        // полученный в предыдущем вызове
                                // хэндл соединения с сервером
   pnstr8          objName,     // имя пользователя в базе сервера
   nuint16         objType,
   pnstr8          password );  // пароль для регистрации

Результат выполнения данной функции может говорить как об успехе (известный уже нам SUCCESSFUL, либо ALREADY_ATTACHED), так и о неудаче (по разным причинам, к примеру 0x8801 - неверный хэндл соединения, либо 0x89DF - истек срок действия пароля).
ПРИМЕЧАНИЕ
Коды возврата NetWare API в большинстве своем уникальны, и их смысл не зависит от контекста, в котором они получены (это не так лишь за редким исключением). Полностью с кодами возврата можно ознакомиться в документации NDK, либо в справке приложения NetWare Administrator.

Не знаю, догадались вы к настоящему моменту, или нет, но имеется несколько тонких моментов, которые нужно учесть (а иначе зачем бы я стал писать эту статью?). Несоблюдение приведенных ниже правил приведет к ошибке регистрации пользователя. Итак:

Давайте взглянем на реальный код, выполняющий регистрацию пользователя на bindery-сервере.
//-------------------------------------------------------------
// LoginToNWServer
// Комментарии: функция-диспетчер - если затребован логин на bindery-сервер,
//              то выполняет подготовку параметров и логин
//              Если же выполняется NDS-логин, происходит вызов 
//              LoginToDSServer()
//
DWORD LoginToNWServer( 
    UINT  connID, 
    char* user, 
    char* password, 
    char* context, 
    int   binderyLogin )
{
    DWORD ccode = 0xFFFFFFFF;
    WORD  connFlags;

    connFlags = GetConnectionFlagsByConnId( connID );
    if( !( connFlags & CONNECTION_LICENSED ) )
    {
        if( !binderyLogin )
        {
            // NDS login
            ccode = LoginToDSServer( connID, user, password, context );
        }
        else
        {
            // Bindery login
            PrepareStringToNW( user );
            CharToOem( user, user );
            PrepareStringToNW( password );
            CharToOem( password, password );
            ccode = NWLoginToFileServer( connID, user, OT_USER, password );
        }
        if( !(   ccode == 0      /*    SUCCESSFUL    */
              || ccode == 0x8800 /* ALREADY_ATTACHED */
             ) )
        {
            char str[256];
            LoadString( 
                hNWSrvInst, 
                Localized( LOGIN_ERROR ), 
                str, 
                sizeof( str )
                 );
            ShowError( &ccode, str );
        }
    }
    return ccode;
}

//-------------------------------------------------------------
// PrepareStringToNW
// Комментарии: приводим все символы к верхнему регистру,
//              меняем пробелы на underscore'ы.
//
char* PrepareStringToNW( char* s )
{
  CharUpper( s );
  for( int i = 0; i < lstrlen( s ); i++ )
  {
      if( s[i] == ' ' ) 
        s[i] = '_';
  }
  return s;
}
//-------------------------------------------------------------

То, что мы обсуждали до настоящего момента, присутствует в приведенном коде после комментария // Bindery login. А вот то, что скрывается за вызовом LoginToDSServer() - это уже имеет отношение к следующему поколению серверов NetWare. Итак...

Сервер NetWare 4.x

В целях сохранения программной совместимости, сервер NetWare 4.x эмулирует сервер версии 3.x (для этого ему нужно задать параметр BINDERY_CONTEXT, но детали этих манипуляций относятся уже к области администрирования), так что для настроенного подобным образом сервера вы вполне можете использовать описанные выше методики. Но мы ведь не ищем легких путей? Тем более что за легкость обязательно придется заплатить сужением возможностей. Поэтому мы рассмотрим механизмы регистрации пользователей, предоставляемые серверами 4-й версии.

Деревья, реплики, контексты, объекты...

Объекты сети, учитываемые и уникально идентифицируемые по своим именам, сервер NetWare 3.x хранит в базе, называемой BINDERY. Это название точно отражает сущность отношений между объектами и серверами - они привязаны (bind - англ. привязывать) к определенному серверу сети, т.е. каждый объект может быть однозначно идентифицирован в пределах только одной базы BINDERY. Чтобы иметь возможность регистрироваться на других серверах NetWare 3.x, пользователь должен быть зарегистрирован на каждом из этих серверов, причем при каждом подключении он обязан вводить имя/пароль.

Серверы NetWare 4.x ведут себя иначе - несколько серверов могут быть объединены в группу, поддерживающую единую базу учета объектов сети, построенную по иерархическому принципу, и из-за этого носящую имя дерева. Каждый сервер несет на себе копию (реплику) дерева, и, в зависимости от распределения ролей между серверами, определенным образом периодически ее синхронизирует с остальными репликами своего дерева, поддерживая таким образом интегральную целостность базы учета объектов, несмотря на возможную территориальную разнесенность серверов. Все объекты дерева представляют те или иные ресурсы сети NetWare и управляются единой службой каталогов Novell (NDS - Novell Directory Service).

Каждый объект NDS (в том числе и пользователь, а нас сейчас интересует именно он) имеет уникальное полное имя, складывающееся из его собственного имени и контекста, в котором он расположен. В общих чертах термин "контекст" можно определить как имя контейнерного объекта, содержащего данный объект (понятно, что сам контейнерный объект в свою очередь может находиться в другом контейнере более высокого уровня, лежащего ближе к корню дерева - объекту [Root]). Служба каталогов поддерживает очень удобную функцию - единую регистрацию объекта в дереве. Зарегистрировававшись в дереве, пользователю нет необходимости регистрироваться на каждом из серверов дерева - система сделает это сама в фоновом режиме, не запрашивая пароль. Этот процесс называется аутентификацией (authentication - запомним этот термин, он поможет в дальнейшем понять логику вызова функций при регистрации пользователя).
ПРИМЕЧАНИЕ
Следует заметить, что для NDS-функций уже нет необходимости в преобразовании строк в верхний регистр и замене пробелов.

Login to...

Итак, для регистрации на NDS-сервере необходимо сначала выполнить логин в NDS, указав имя пользователя, его контекст и пароль.

NWDSCCODE NWDSLogin(

   NWDSContextHandle   context,
   nflag               optionsFlag,      // не используется, 0
   pnstr8              objectName,
   pnstr8              password,
   nuint32             validityPeriod ); // не используется, 0

Наибольший интерес в приведенном выше прототипе вызывает хэндл контекста. Ведь выше мы говорили, что контекст - это лишь имя контейнера, содержащего в себе объект. Да, действительно это так. Но для API-функций контекст - это нечто большее чем строка, это некая структура в памяти, содержащая дополнительно имя дерева, для которого мы устанавливаем контекст, и флаги, отвечающие за некоторые тонкости восприятия строк контекста функциями NetWare API. Данная статья не предназначена для полного освещения этих вопросов (тем более, что полную информацию можно и нужно получить, познакомившись с документацией NDK), укажем лишь, что вам необходимо выделить память под структуру контекста, настроить его и после использования освободить. Вот фрагмент кода, взятый из демонстрационного проекта, дающий пример манипуляций с контекстом.

//-------------------------------------------------------------
// AllocateContext
//
DWORD AllocateContext()
{
    LCONV lc;

    NWLlocaleconv( &lc );
    NWInitUnicodeTables( lc.country_id, lc.code_page );
    return NWDSCreateContext();
}

//-------------------------------------------------------------
// AllocateContextForServer
// Комментарии: выделяет контекст, настраивает его согласно
//              указанных флагов и возвращает имя дерева 
//              сервера
//
DWORD AllocateContextForServer( UINT connID, char* tree, DWORD cxFlags )
{
    char treeName[NW_MAX_TREE_NAME_LEN] = "";

    if( !NWIsDSServer( connID, treeName ) ) 
        return 0xFFFFFFFF;
    CleanNWString( treeName );
    NWDSContextHandle context = AllocateContext();
    if( context == 0xFFFFFFFF ) 
        return 0xFFFFFFFF;
    NWDSSetContext( context, DCK_TREE_NAME, treeName );
    if( cxFlags )
    {
        DWORD flags;

        NWDSGetContext( context, DCK_FLAGS, &flags );
        flags |= cxFlags;
        NWDSSetContext( context, DCK_FLAGS, &flags );
    }
    if( tree )
        lstrcpy( tree, treeName );
    return 0;
}

//-------------------------------------------------------------
// FreeContext
//
void FreeContext( DWORD context )
{
    NWDSFreeContext( context );
    NWFreeUnicodeTables();
}

//-------------------------------------------------------------

Как можно видеть, при выделении контекста необходимо настроить таблицы UNICODE, указав код страны и используемую кодовую страницу (эту информацию можно получить из внутреннего состояния клиента NetWare вызовом NWlocaleconv()). Если рассмотреть подробнее последовательность вызовов внутри AllocateContextForServer(), можно увидеть ряд интересных моментов. Оказывается, имя дерева, в которое включен сервер, можно получить по его хэндлу соединения.
Имя дерева возвращается во внутреннем формате NDS, т.е. дополненное до 32 символов underscore'ами, что выглядит весьма непривлекательно, поэтому их надо бы отрезать, что и делает наша функция CleanNWString().
Поскольку свежевыделенный контекст ненастроен, то необходимо внести в него информацию о флагах и имени дерева, чем и занимается NWDSSetContext().

Теперь мы готовы к регистрации пользователя на NDS-сервере.

//-------------------------------------------------------------
// LoginToDSServer
// Комментарии: логин на сервер, поддерживающий NDS
//
DWORD LoginToDSServer( NWCONN_HANDLE connID, char* user, char* pass, char* specContext )
{
  DWORD ccode   = 0;
  DWORD context = 0xFFFFFFFF;
  char  defaultContext[256];
  char  treeName[NW_MAX_TREE_NAME_LEN];

  // выделим контекст для операций с NDS
  context = AllocateContextForServer( connID, treeName );
 
  // определим флаги соединения с сервером
  WORD connFlags = GetConnectionFlagsByConnId( connID );
  
  if( connFlags & CONNECTION_LOGGED_IN )
  {
      // на этот сервер уже кто-то залогинен, отлогиним его
      if( connFlags & CONNECTION_NDS )
         ccode = NWDSLogout( context );
      else
         ccode = NWLogoutFromFileServer( connID );
  }
  if( !ccode )
  {
    // раз ошибок не было, можно приступать к логину

    // настроим контекст
    if( !GetDefCX( treeName ) )
    {
        if( !GetRegistryDefCX( treeName ) )
            *defCx = '\0';
    }
    if( specContext == NULL )
    {
        // не указан пользовательский контекст

        if( !*defCx )           // и контекст по-умолчанию также отсутствует - 
        {
            FreeContext( context );
            return 0xFFFFFFFF;  // уходим...
        }

        // воспользуемся контекстом по-умолчанию                        
        lstrcpy( defaultContext, defCx );
    }
    else
        lstrcpy( defaultContext, specContext );
   
   ccode = NWDSSetContext( context, DCK_NAME_CONTEXT, defaultContext );
   if( ccode ) 
   {
       ShowError( &ccode, "Setting Context Name" );
       FreeContext( context );
       return ccode;
   }

   // зарегистрируем нашего пользователя в дереве
   ccode = NWDSLogin( context, 0, user, pass, 0 );
   if(    ccode == 0       /*    SUCCESSFUL    */
       || ccode == 0x8800  /* ALREADY_ATTACHED */
      )
   {
     // а теперь аутентифицируем его на сервере
     ccode = NWDSAuthenticate( connID, 0, NULL );
     if( ccode && ccode != 0x8800 )
         ShowError( &ccode, "LoginToDSServer" );
     // мы закончили, уберем за собой
     FreeContext( context );
     return ccode;
   }
  }

  // увы, нас постигла неудача
  FreeContext( context );
  return ccode;
}

//-------------------------------------------------------------

Сразу отмечу, что функции GetDefCX() и GetRegistryDefCX() особого интереса не представляют и являются лишь средством получить строку контекста по-умолчанию (мы отсылаем читателя к исходным текстам демонстрационного проекта за деталями их реализации), а вот то, что действительно представляет интерес, мы рассмотрим поближе.

Как мы помним, необходимо сразу же выделить контекст, посредством которого мы сообщим, в какое дерево мы собираемся зарегистрировать нашего пользователя. Далее в данной реализации производится определение, зарегистрирован ли кто на данном сервере в настоящий момент (имеется ввиду, конечно же, зарегистрирован с нашей рабочей станции), и если это так, то производится отключение текущего пользователя (вы вполне можете посчитать это нерациональным и изменить поведение функции, чтобы она, к примеру, возвращала признак неудачного завершения функции, не забыв при этом освободить выделенный контекст). Далее производится окончательная настройка структуры контекста - в нее добавляется строка контекста, используемого для входа, - после чего и выполняется попытка регистрации пользователя.

Как можно видеть, сразу после успешного вызова NWDSLogin() мы выполняем NWDSAuthenticate() (вот она - аутентификация, о которой мы говорили раньше), потому что простой логин выполнит лишь регистрацию пользователя в NDS, а вот реальное подключение к ресурсам сервера (то, к чему в итоге мы и стремимся) возможно лишь после успешного вызова NWDSAuthenticate().

Ну вот, так или иначе, мы подключились к серверу NetWare. Что дальше?..

А что же дальше?

Дальше логично было бы предположить, что ресурсами файл-сервера надо бы воспользоваться по назначению, т.е. выполнить подключение того или иного каталога сервера в качесте сетевого диска Windows. В этом нам может помочь MPR (Multiprovider Redirector) нашей операционной системы. С тем же успехом можно воспользоваться и API нашего сетевого NetWare-клиента. Но, как мы можем заметить, эта тема уже вышла за пределы рассматриваемого в данной статье вопроса, и, следовательно, это уже совершенно другая история...

Несколько слов о демонстрационном проекте. Он представляет из себя dll, предоставляющую возможность выполнить присоединение к NetWare-серверу на основе описанных в данной статье методик. В модуль включена экспортируемая функция LoginToServer(), которая сразу попытается подключить пользователя к выбранному серверу, либо, в случае неудачной попытки подключения, выведет диалог, в котором можно указать правильные атрибуты пользователя для подключения.
Кроме того, модуль включает в себя ряд сервисных функций, позволяющих
  • получить список NetWare-серверов в вашей сети;
  • получить имя пользователя, зарегистрированного в настоящий момент на выбранном сервере;
  • получить максимальное число соединений, поддерживаемое сервером;
  • получить флаги текущего соединения;
  • получить значение контекста по-умолчанию для выбранного дерева (а NetWare Client32 поддерживает возможность одновременного подключения к серверам нескольких деревьев).
Для успешной сборки необходимо иметь установленный NDK (начиная с версии для NW4.1, с более ранними автор подробно не знаком), пути к каталогам INCLUDE и LIB (если точнее, то LIB\%PLATFORM%\MSCVC или LIB\%PLATFORM%\BORLAND, где вместо %PLATFORM% должен быть каталог целевой платформы - W95, WNT32) которого должны быть известны вашему любимому компилятору (эта dll собиралась VC++ 6.0 с PLATFORM = WNT32, это ясно видно в файле nwsrv32.dsp).
Если вы пользуетесь NDK для NW5.x, то структура каталогов LIB будет иная - нас будет интересовать LIB\WIN32\.
И, если уж мы затронули тему целевых платформ, то обращу ваше внимание на объявление #define N_PLAT_MSW. Оно сообщает о том, что данный модуль собирается под MS Windows, что вкупе с предопределенным вашим компилятором объявлением #define _WIN32 даст исчерпывающую информацию о целевой платформе для сборки. При его отсутствии вы получите кучу загадочных сообщений об ошибках компиляции.

Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 0        Оценить