6. База данных объектов

Файл-сервер Novell NetWare содержит базу данных объектов, в которой есть сведения о ресурсах, доступных в сети (файл-серверы, серверы печати и т. п.), о пользователях и группах пользователей и т. д. Эта база данных называется Bindery. Физически она находится в двух скрытых файлах с именами net$bind.sys и net$bval.sys, расположенных в каталоге SYS:SYSTEM.

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

Например, каждый пользователь, добавленный супервизором сети или администратором, становится объектом, имеющим имя и идентификатор. Тип такого объекта соответствует значению 1, и это статический объект. Статический объект хранится в базе до тех пор, пока он не будет удален явным образом.

Если в сети имеются несколько файл-серверов, то для каждого сервера в базе Bindery имеются объекты, соответствующие всем серверам, активным в сети. Это временные объекты, которые удаляются автоматически, когда соответствующий файл-сервер прекращает свою работу. Вы можете воспользоваться этим обстоятельством для получения списка активных серверов, имеющихся в сети.

Программы пользователя могут не только считывать содержимое базы объектов, но и добавлять свои собственные записи, а также редактировать имеющиеся. Для сокращения объема книги мы рассмотрим только способы извлечения информации из базы Bindery. Полностью работа с базой данных объектов описана в документации, поставляющейся вместе с библиотекой Novell NetWare C Interface.

6.1. Объекты, коды объектов и права доступа

База данных Bindery хранит объекты, которые имеют такие атрибуты, как идентификатор, имя, тип, флаг (статический или динамический объект), байт доступа.

Идентификатор объекта - число размером 4 байта, которое является уникальным для данного файл-сервера. Никакие два объекта, сведения о которых хранятся в одной базе данных на одном сервере, не могут иметь одинаковые идентификаторы.

Имя объекта представляет собой текстовую строку размером не более 47 байт. В операциях поиска объектов в базе данных по имени в поле имени допускается указывать символы "*" и "?", которые интерпретируются обычным способом, как и в MS-DOS.

Библиотека NetWare C Interface имеет функции, позволяющие просматривать список всех объектов базы данных, искать объекты определенного типа или по имени с использованием шаблона и символов "*" и "?". По идентификатору объекта вы можете легко получить его имя и наоборот, по имени можно узнать идентификатор объекта.

Тип объекта определяет сетевой ресурс и может принимать следующие значения:

Значение Описание
0 Неклассифицируемый (неизвестный) объект
1 Пользователь
2 Группа пользователей
3 Очередь печати
4 Файл-сервер
5 Сервер заданий
6 Шлюз
7 Сервер печати
8 Очередь для архивирования
9 Сервер для архивирования
A Очередь заданий
B Администратор
24 Сервер удаленного моста

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

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

Байт доступа используется для определения прав, необходимых для поиска, чтения, создания, редактирования или удаления объекта. Две тетрады байта отвечают за доступ на чтение и доступ на запись. Младшие четыре бита байта доступа отвечают за чтение, старшие - за запись.

Для тетрад определены значения от 0 до 4 - уровни доступа. Для того чтобы пользователь или другой объект получили доступ, тетрады его собственного байта доступа должны иметь значения, равные или превышающие значения в байте доступа объекта, к которому запрашивается доступ.

Приведем список возможных значений для уровней доступа:

0 Anyone Объект не подключен к файл-серверу
1 Logged Объект подключен к файл-серверу
2 Object Объект подключен к файл-серверу с именем и паролем
3 Supervisor Объект имеет права супервизора
4 NetWare Объект имеет права операционной системы Novell NetWare

Для определения собственного уровня доступа и идентификатора пользователя программа может воспользоваться функцией GetBinderyAccessLevel():

int GetBinderyAccessLevel(BYTE *SecurityAccessLevel,
     long *ObjectID);


Первый параметр этой функции указывает на слово, в которое будет записан уровень доступа, второй - на двойное слово, в которое будет записан идентификатор пользователя.

Вместо функции GetBinderyAccessLevel() можно использовать функцию E3h прерывания INT 21h:

На входе: AH = E3h;
DS:SI = Адрес буфера запроса;
ES:DI = Адрес буфера ответа.
На выходе: AL = Код ошибки или 0, если операция завершилась без ошибок.

Буфер запроса имеет следующий формат:

struct REQUEST {
        WORD    PacketLength;        // размер пакета запроса
        BYTE    Function;            // должно быть равно 70
};


Буфер ответа имеет следующий формат:

struct REPLAY {
        WORD    PacketLength;        // размер пакета
        BYTE    SecurityAccessLevel; // уровень доступа
        long    ObjectID;            // идентификатор объекта
};


По идентификатору объекта вы можете получить его имя и тип с помощью функции GetBinderyObjectName():

 int GetBinderyObjectName(long ObjectID,
     char *ObjectName, WORD *ObjectType);


Для объекта, идентификатор которого задан первым параметром, функция возвращает имя объекта и его тип, записывая их в области памяти, указанные при помощи второго и третьего параметров.

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибки Значение
0x96 Мало памяти на сервере
0xFC В базе нет объекта с указанным идентификатором
0xFE База данных Bindery заблокирована
0xFF Сбой базы данных Bindery

Вместо функции GetBinderyObjectName() вы также можете использовать функцию E3h прерывания INT 21h. При этом необходимо использовать форматы буфера запроса и буфера ответа, приведенные ниже.

Буфер запроса:

struct REQUEST {
        WORD    PacketLength;        // размер пакета запроса
        BYTE    Function;            // должно быть равно 54
        long    ObjectID;            // идентификатор объекта
};


Буфер ответа:

struct REPLAY {
        WORD    PacketLength;        // размер пакета
        long    ObjectID;            // идентификатор объекта
        WORD    ObjectType;          // тип объекта
        BYTE    ObjectName[48];      // имя объекта
};


Для получения идентификатора объекта по его имени и типу вы можете воспользоваться функцией GetBinderyObjectID():

 int GetBinderyObjectID(char *ObjectName,WORD ObjectType,
     long *ObjectID);


Имя и тип объекта задаются первым и вторым параметрами, идентификатор записывается по адресу, заданному третьим параметром.

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибки Значение
0x96 Мало памяти на сервере
0xEF Имя объекта указано неправильно
0xF0 Не допускаются символы шаблона "*", "?"
0xFC В базе нет объекта с указанным идентификатором
0xFE База данных Bindery заблокирована
0xFF Сбой базы данных Bindery

Вместо функции GetBinderyObjectID() можно использовать функцию E3h прерывания INT 21h. Приведем форматы буфера запроса и буфера ответа.

Буфер запроса:

struct REQUEST {
        WORD    PacketLength;        // размер пакета запроса
        BYTE    Function;            // должно быть равно 53
        long    ObjectType;          // тип объекта
        BYTE    ObjectNameLength;    // длина имени объекта
        BYTE    ObjectName[ObjectNameLength]; // имя объекта
};


Буфер ответа:

struct REPLAY {
        WORD    PacketLength;        // размер пакета
        long    ObjectID;            // идентификатор объекта
        WORD    ObjectType;          // тип объекта
        BYTE    ObjectName[48];      // имя объекта
};


6.1.1. Программа BACCESS

Приведем текст программы BACCESS (листинг 26), которая определяет и выводит на экран имя, идентификатор, тип объекта, а также его уровень доступа. В программе используются описанные выше функции.

// ===================================================
// Листинг 26. Программа для просмотра уровня
// доступа рабочей станции
// Файл baccess\baccess.cpp
//
// (C) A. Frolov, 1993
// ===================================================

#include <stdlib.h>
#include <stdio.h>

#define WORD unsigned int
#define BYTE unsigned char

extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int GetBinderyAccessLevel(BYTE *, long *);
extern "C" int GetBinderyObjectName(long, char*, WORD*);

void main(void) {

        char MajorVersion=0;
        char MinorVersion=0;
        char Revision=0;
        int ccode;

        BYTE SecurityAccessLevel;
        long ObjectID;
        char ObjectName[48];
        WORD ObjectType;

        printf("\n*BACCESS* (C) Frolov A., 1993\n");

// Проверяем присутствие сетевой оболочки

        asm push si
        GetNetWareShellVersion(&MajorVersion,
                        &MinorVersion, &Revision);
        asm pop si

        if(MajorVersion == 0) {
                printf("\nОболочка NetWare не загружена\n");
                return;
        }

// Получаем свой идентификатор и уровень доступа

        GetBinderyAccessLevel(&SecurityAccessLevel, &ObjectID);

// По идентификатору определяем свое имя

        ccode =  GetBinderyObjectName(ObjectID, ObjectName,                                                                      &ObjectType);

// Если пользователь подключился к файл-серверу,
// выводим его имя, идентификатор и тип

        if(!ccode) {
                printf("Пользователь %s, ID = %lX, Type = %d\n",
                        ObjectName, ObjectID, ObjectType);
        }

// Выводим права доступа на чтение

        printf("Права доступа на чтение:\t");

        switch(SecurityAccessLevel & 0x0f) {
                case 0:
                        printf("Anyone\t(не подключен к файл-серверу)\n");
                        break;
                case 1:
                        printf("Logged\t(подключен к файл-серверу)\n");
                        break;
                case 2:
                        printf("Object\t(подключен к файл-серверу "
                                  "с именем и паролем)\n");
                        break;
                case 3:
                        printf("Supervisor\t(права супервизора)\n");
                        break;
                case 4:
                        printf("NetWare\t(права Novell NetWare)\n");
                        break;
        }

// Выводим права доступа на запись

        printf("Права доступа на запись:\t");

        switch((SecurityAccessLevel >> 4) & 0x0f) {
                case 0:
                        printf("Anyone\t(не подключен к файл-серверу)\n");
                        break;
                case 1:
                        printf("Logged\t(подключен к файл-серверу)\n");
                        break;
                case 2:
                        printf("Object\t(подключен к файл-серверу "
                                  "с именем и паролем)\n");
                        break;
                case 3:
                        printf("Supervisor\t(права супервизора)\n");
                        break;
                case 4:
                        printf("NetWare\t(права Novell NetWare)\n");
                        break;
        }
}


6.2. Просмотр базы объектов

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

Для получения списка объектов, сведения о которых хранятся в базе данных Bindery, предназначена функция ScanBinderyObject():

int ScanBinderyObject(char *SearchObjectName,
                WORD SearchObjectType, long *ObjectID, char *ObjectName,
                WORD *ObjectType, char *ObjectHasProperties,
                char *ObjectFlag, char *ObjectSecurity);


Эта функция должна использоваться в цикле. При первом вызове в переменную, на которую указывает параметр ObjectID, необходимо записать значение -1. В дальнейшем в эту переменную будет записываться идентификатор найденного объекта.

Для поиска следует указать имя объекта (параметр SearchObjectName) и тип объекта (параметр SearchObjectType). В качестве имени объекта можно использовать шаблон с символами "*" и "?". Тип объекта может быть задан конкретно, либо можно указать значение -1. В последнем случае функция будет искать объекты всех типов. Для того чтобы найти все объекты всех типов, в качестве имени надо указать строку "*", в качестве типа задать значение -1.

Для найденных объектов в соответствующие переменные, указанные параметрами функции, будут записаны имя объекта (параметр ObjectName), тип объекта (параметр ObjectType), флаг (параметр ObjectFlag), байт доступа (параметр ObjectSecurity). Кроме того, в переменную, на которую указывает параметр ObjectHasProperties, записывается значение 0xFF, если объект имеет дополнительную связанную с ним информацию (Properties), которую можно извлечь специально предназначенными для этого функциями.

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибки Значение
0x96 Мало памяти на сервере
0xEF Имя объекта указано неправильно
0xFC В базе нет объекта с указанным идентификатором
0xFE База данных Bindery заблокирована
0xFF Сбой базы данных Bindery

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

Вместо функции ScanBinderyObject() можно использовать функцию E3h прерывания INT 21h:

На входе: AH = E3h;
DS:SI = Адрес буфера запроса;
ES:DI = Адрес буфера ответа.
На выходе: AL = Код ошибки или 0, если операция завершилась без ошибок.

Буфер запроса имеет следующий формат:

struct REQUEST {
        WORD    PacketLength;        // размер пакета запроса
        BYTE    Function;            // должно быть равно 55
        BYTE    ObjectID;            // идентификатор объекта
        WORD    SearchObjectType;    // тип объекта
        BYTE    NameLength;          // длина имени образца для
                                     //   поиска объекта
        BYTE    SearchObjectName[NameLength]; // имя образца для
                                     //   поиска объекта
};


Буфер ответа имеет следующий формат:

struct REPLAY {
        WORD    PacketLength;        // размер пакета
        long    ObjectID;            // идентификатор объекта
        WORD    ObjectType;          // тип объекта
        BYTE    ObjectName[48];      // имя объекта
        BYTE    ObjectFlag;          // флаг объекта
        BYTE    SecurityAccessLevel; // уровень доступа
        BYTE    ObjectHasProperties; // есть записи
};


В базе данных объектов Bindery с каждым объектом может быть связано несколько дополнительных записей, содержащих данные (property). Каждая такая запись имеет свое имя, флаг и байт доступа. Если объект имеет записи, вы можете получить список их имен и других атрибутов при помощи функции ScanProperty():

int ScanProperty(char *ObjectName, WORD ObjectType,
      char *SearchPropertyName, long *SequenceNumber,
      char *PropertyName, char *PropertyFlag,
      char char *PropertySecurity,
      char *PropertyHasValue, char *MoreProperties);


Функция должна вызываться в цикле. При первом вызове переменная, на которую указывает параметр SequenceNumber, должна содержать значение -1. При последующих вызовах содержимое этой переменной будет изменяться автоматически.

Для считывания полей функции необходимо указать имя сканируемого объекта (параметр ObjectName), тип объекта (параметр ObjectType), а также имя записи или шаблон имени записи (параметр SearchPropertyName). В шаблоне можно использовать символы "*" и "?".

Для найденных записей в соответствующие переменные, указанные параметрами функции, будут записаны имя записи (параметр PropertyName), флаг записи (параметр PropertyFlag), байт доступа (параметр PropertySecurity), признак того, что запись имеет значения (параметр PropertyHasValue), признак того, что в объекте есть еще и другие записи (MoreProperties).

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибки Значение
0x96 Мало памяти на сервере
0xF1 Неправильный код доступа
0xFB Указанная запись не найдена
0xFC В базе нет объекта с указанным идентификатором
0xFE База данных Bindery заблокирована
0xFF Сбой базы данных Bindery

В Novell NetWare для каждого типа объекта существует определенный набор записей, которые могут быть связаны с этим объектом. Например, с объектом типа 1 (обычный пользователь) связаны такие записи, как PASSWORD (пароль) и SECURITY_EQUALS (эквивалентность прав доступа). Содержимое записей можно считать при помощи функции ReadPropertyValue(), которая описана в документации по библиотеке NetWare C Interface. Для этого пользователь, запустивший программу, должен обладать достаточным уровнем доступа.

Приведем некоторые имена полей, определенных в NetWare:

Имя записи Тип объекта Доступ, запись/чтение
BLOCKS_READ Файл-сервер 3/1
BLOCKS_WRITTEN Файл-сервер 3/1
CONNECT_TIME Файл-сервер 3/1
GROUP_MEMBERS Группа пользователей 3/1
GROUPS_I'M_IN Пользователь 3/1
IDENTIFICATION Пользователь 3/1
NET_ADDRESS Файл-сервер 4/0
OLD_PASSWORDS Пользователь 3/3
OPERATORS Файл-сервер 3/3
PASSWORDS Пользователь 4/4
SECURITY_EQUALS Пользователь 3/2

Полный список полей и подробное их описание вы найдете в документации по библиотеке NetWare C Interface.

6.2.1. Программа BSCAN

Приведем исходный текст программы BSCAN (листинг 27), которая просматривает базу данных объектов. Для каждого найденного объекта программа выводит имя и расшифрованный тип объекта, флаг и уровень доступа. Если объект имеет дополнительные записи (properties), вызывается функция, которая выводит имена найденных записей.

// ===================================================
// Листинг 27. Программа для просмотра содержимого
// базы данных объектов
// Файл bscan\bscan.cpp
//
// (C) A. Frolov, 1993
// ===================================================

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define WORD unsigned int
#define BYTE unsigned char

extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int ScanBinderyObject(char *, WORD, long *,
                                        char *, WORD *, char *, char *, char *);
extern "C" int ScanProperty(char *, WORD, char *, long *,
                                        char *, char *, char *, char *, char *);

void Property(char *ObjectName, WORD ObjectType);

void main(void) {

        char MajorVersion=0;
        char MinorVersion=0;
        char Revision=0;
        int ccode;

        BYTE ObjectSecurity;
        long ObjectID;
        char SearchObjectName[48];
        char ObjectName[48];
        WORD SearchObjectType;
        WORD ObjectType;
        char ObjectHasProperties;
        char ObjectFlag;

        printf("\n*BSCAN* (C) Frolov A., 1993\n");

// Проверяем присутствие сетевой оболочки

        asm push si
        GetNetWareShellVersion(&MajorVersion,
                        &MinorVersion, &Revision);
        asm pop si

        if(MajorVersion == 0) {
                printf("\nОболочка NetWare не загружена\n");
                return;
        }

// Просматриваем в цикле содержимое базы объектов,
// ищем объекты всех типов

        SearchObjectType = -1;

// Маска для поиска всех объектов

        strcpy(SearchObjectName, "*");

        for(ObjectID = -1;;) {

// Получаем очередной объект

                ccode = ScanBinderyObject(SearchObjectName,
                  SearchObjectType, &ObjectID, ObjectName, &ObjectType,
                  &ObjectHasProperties, &ObjectFlag, &ObjectSecurity);

// Если больше нет объектов или произошла ошибка, завершаем цикл

                if(ccode) break;

// Выводим имя и тип объекта

                printf("\n%-18s\t", ObjectName);

                switch(ObjectType) {
                case 0:
                        printf("???                     "); break;
                case 1:
                        printf("Пользователь            ");        break;
                case 2:
                        printf("Группа                  ");      break;
                case 3:
                        printf("Очередь на печать       "); break;
                case 4:
                        printf("Файл-сервер             "); break;
                case 5:
                        printf("Сервер заданий          "); break;
                case 6:
                        printf("Шлюз                    "); break;
                case 7:
                        printf("Сервер печати           "); break;
                case 8:
                        printf("Очередь архивирования   "); break;
                case 9:
                        printf("Сервер для архивирования"); break;
                case 0xA:
                        printf("Очередь заданий         "); break;
                case 0xb:
                        printf("Администратор           "); break;
                case 0x26:
                        printf("Сервер удаленного моста "); break;
                default:
                  printf("Объект 0x%04.4X           ", ObjectType); break;
                }

// Выводим флаг объекта, который может иметь два значения:
// 0 для постоянных объектов и 1 для временных

                if(ObjectFlag) printf("Временный ");
                else           printf("Постоянный");

// Выводим байт прав, необходимых для получения доступа к объекту

                printf(" Доступ %02.2X", ObjectSecurity);

// Если для объекта имеются дополнительные записи,
// выводим их названия

                if(ObjectHasProperties) Property(ObjectName,
                        ObjectType);
        }
}

// =================================================================
// Функция Property выводит названия дополнительных записей объектов
// =================================================================

void Property(char *ObjectName, WORD ObjectType) {

        int ccode;

        BYTE PropertySecurity;
        long ObjectID;
        char SearchPropertyName[16];
        char PropertyName[16];
        WORD SearchObjectType;
        char PropertyFlag;
        long SequenceNumber;
        char PropertyHasValue;
        char MoreProperties;

// Маска для поиска всех записей

        strcpy(SearchPropertyName, "*");
        for(SequenceNumber=-1;;) {

// Получаем запись

                ccode = ScanProperty(ObjectName, ObjectType,
                  SearchPropertyName, &SequenceNumber,
                  PropertyName, &PropertyFlag, &PropertySecurity,
                  &PropertyHasValue, &MoreProperties);

// Если записей больше нет, завершаем цикл

                if(ccode) break;

// Выводим название записи

                printf("\n\tProperty %s", PropertyName);
        }
}