Ad cogitandum et agendum homo natus est
Мыслить и действовать рожден человек

 

Вот и настал светлый миг в твоей жизни - трясущимися после вчерашнего руками ты возишь мышку по давно немытому коврику, в надежде выцепить из Сети чего-нибудь новенького и полезненького и немедленно воплотить его в жизнь. Как в песне поется "на радость народу, врагам на погибель". Могу сказать тебе - зайдя на сайт ][ ты не ошибся адресом, да ты и сам это знаешь. Что ж, приступим.

Песня о LANе

Поговорим сегодня о LAN'e, она же Local Area Network, она же ЛВС, а в просторечии локалка или локальная вычислительная сеть. Как ты помнишь, локалки бывают одноранговые и с выделенным сервером. Простейшая одноранговая сеть делается обычно по причине а) лени б) бедности. Яркий пример: есть, например, в офисе два компьютера и один принтер. Кому поставить принтер? Не вопрос - конечно, себе, любимому. Тем более, если он цветной :). Однако если сосед по комнате - твой начальник, то принтер будет стоять у него, можешь мне поверить. Он тоже себя любит больше, чем тебя. Как ни странно. А для того, чтобы не чувствовать себя обделенным таким нужным сервисом (на чем же тёток из Интернета ты будешь печатать?), ты идешь в первый попавшийся компьютерный магазин (хотя можно пойти и во второй или третий), покупаешь две сетевые карточки, кусок кабеля, коннекторы-терминаторы. Карточки вставляешь в компы, соединяешь их кабелем, устанавливаешь протокол TCP/IP, делаешь себе доступ к принтеру, и - дело в шляпе. Хотя с другой стороны, можно всего этого и не делать. Можно бегать к своему сокамернику с пачкой дискет и дрожащим голосом просить распечатать парочку файлов. И если тебя через неделю не пошлют на три хорошо известные буквы, то через две недели тебя просто уволят. Опять же, можно купить еще один цветной лазерный или струйный принтер, но если ты не любимый племянник директора или, на худой конец, дяди Билла, в чем лично я что-то сомневаюсь, то, скорее всего, этого тебе сделать не дадут.

Но не будем о печальном. Одноранговая сетка - это конечно хорошо. Но для централизации управления доступом к ресурсам БОЛЬШОЙ КОРПОРАТИВНОЙ СЕТИ ставят сервер, часто под управлением Windows NT/2000, организовывают домен, создают глобальные и локальные группы, учетные записи пользователей, содержащие их логины и пароли, а также другую информацию. А вот это уже интересно - ведь ты бы хотел посмотреть, какие группы есть на сервере, кто имеет право входить в сетку твоей конторы, да и вообще, если ты читаешь ][, то я думаю у тебя еще не атрофировалось самое человеческое качество (это я о любопытстве).

Тут, как зачастую бывает в жизни, есть несколько путей-дорог. Можно сходить к админу и попросить его показать структуру домена, учетные записи пользователей, а заодно и ключ от квартиры, где деньги лежат. Итог этой беседы, скорее всего, будет печален для одной из сторон. Не будем конкретизировать, для какой. Можно воспользоваться утилитами, которые входят в состав Windows NT/2000 (ты же не пользуешься Win9x, я надеюсь?), можно взять софтину "стороннего производителя", а можно, как говорил классик, пойти другим путём. Вот мы им и пойдем. 

Мы идём другим путём

На просторах СНГ любят Делфи. Это - аксиома. Да и как же его не любить, если многие из нас вышли из ДОСа и росли вместе с Turbo/Borland Паскалем, который фирма Борланд долгое время не защищала от копирования. А ни что так не было любимо советским человеком, как халява (по вполне понятным, должен я сказать, и объективным причинам). Вот и плодились по институтам-университетам Союза незаконно размноженные копии программ известной фирмы, на которых учили программистскому делу юных комсомольцев, многие из которых так и не успели стать коммунистами (что радует). Но - хватит лирических отступлений. Перейдем к делу, запустим Делфи и посмотрим, чем она сможет нам помочь в деле запускания своих ручек к LAN в карман.

Как ты уже наверняка знаешь, M$ Windows написана на С, и многие функции этой ОСи собраны в dynamic-link libraries (DLL), которые представляют из себя файлы-библиотеки, естественно, бесплатные :). Использование этих функций и позволит нам узнать много интересного. Итак, из меню Help запустим справку Windows SDK, в строке поиски наберем что-нибудь типа NetServerGetInfo. Нажимаем Enter и видим описание этой функции - кто дружит с аглицким языком, тот поймет, что с ее помощью мы можем получить некую информацию о сервере, указанного типа, видимых в указанном домене. "Всё это хорошо, - скажешь ты. А как мне использовать эту функцию в Делфи?" И будешь совершенно прав в своём праведном возмущении. Для того чтобы что-то использовать, надо это что-то сначала получить. Нажимаем на кнопочку Quick Info и видим такую картину: Import Library netapi32.lib, Header File Imserver.h. То есть для использования этой функции, ее надо импортировать из библиотеки netapi32. Чем мы сейчас и займёмся.

Итак, для того чтобы осуществить эту нехитрую операцию, кинем на форму компоненты класса TListBox(для отображения результатов работы функции), TButton (в обработчике OnClick которого мы будем выполнять полученную функцию), TComboBox (в котором можно будет указывать имя сервера) , а также (для удобства) кнопку закрытия этого приложения. В целом картина должна выглядеть примерно так как на рис.1 (p1_export.jpg).
Затем нужно добавить код, исполняющий функцию и обрабатывающий её результат (листинг 1). Разберём его подробнее. В начале листинга идёт код, традиционно написанный мисс Делфи, в то время как мы своими руками занимались дизайном формы. Далее объявлены типы, необходимые для работы с функцией. Переменная типа NET_API_STATUS будет содержать код результата выполнения функции, а переменная типа PServerInfo101 - указатель на запись с результатами выполнения этой функции. Далее идёт блок кода, содержащий объявления трех функций, необходимых нам для работы. NetApiBufferAllocate и NetApiBufferFree - функции, необходимые для выделения и освобождения области памяти заданного размера, они пригодятся нам при работе с любой функцией из группы портированных из LAN Manager. NetServerGetInfo - одна из функций этой группы, которая, как можно догадаться из названия, выдаёт нам информацию о данном сервере. Всю инфу о типах передаваемых функции переменных и возвращаемых ею значениях смотрим там же, в многострадальном хэлпе Windows SDK. Да, не забудь добавить после объявления функции волшебное слово stdcall, поскольку наша функция будет вызываться из внешнего модуля, который является частью API сей оси. Затем мы должны указать из какой DLL и под какими именами мы с тобой хотим поиметь эти функции и если испарина уже покрыла твой высокий лоб, то можешь отхлебнуть пива, кофе, водки на худой конец или что там плещется в твоем гранёном стакане? И пойдем дальше. 

Листинг 1. Пример использования внешней функции NetServerGetInfo (архив
ExportDllTest.zip
)

unit edllt;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TfrmMain = class(TForm)
btnMain: TButton;
lbMain: TListBox;
cbMain: TComboBox;
lblMain: TLabel;
btnClose: TButton;
procedure btnMainClick(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
end;

//Переменная типа NET_API_STATUS будет содержать код результата выполнения функции Win API
NET_API_STATUS = DWORD;

//переменная типа PServerInfo101 - указатель на запись с результатами выполнения функции Win //API
PServerInfo101 = ^_SERVER_INFO_101;
_SERVER_INFO_101 = record
sv101_platform_id: DWORD;
sv101_name: LPWSTR;
sv101_version_major: DWORD;
sv101_version_minor: DWORD;
sv101_type: DWORD;
sv101_comment: LPWSTR;
end;

//собственно используемая функция
function NetServerGetInfo(servername: LPWSTR; level: DWORD;
var bufptr: Pointer): NET_API_STATUS; stdcall;
// функция для выделения области памяти заданного размера
function NetApiBufferAllocate(ByteCount: DWORD;
var Buffer: Pointer): NET_API_STATUS; stdcall;
//функция освобождения области памяти заданного размера
function NetApiBufferFree(Buffer: Pointer): NET_API_STATUS; stdcall;
//указываем из какой DLL и под какими именами мы будем использовать внешние функции
function NetServerGetInfo; external 'netapi32.dll' name 'NetServerGetInfo';
function NetApiBufferAllocate; external 'netapi32.dll' name 'NetApiBufferAllocate';
function NetApiBufferFree; external 'netapi32.dll' name 'NetApiBufferFree';

var
frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.btnMainClick(Sender: TObject);
const
// level указывает функции, какой тип данных вернуть
level :DWORD = 101;
// в случае успешного выполнения, функция возвращает 0, т.е. NERR_SUCCESS
NERR_SUCCESS = 0;
var
//х содержит результат выполнения функции
x :NET_API_STATUS;
// переменная для хранения NetBIOS имени ПК в формате Unicode
servername :array [0..14] of WideChar;
//bufptr - нетипизированный указатель на область памяти, заполненную в результате выполнения 
bufptr :pointer;
//myInfo - указатель на возвращаемую структуру
myInfo :PServerInfo101;
begin
//очищаем область вывода
lbMain.Items.Clear;
//конвертируем введенное имя ПК в Unicode-строку
StringToWideChar(cbMain.Text, servername,15);
//выделяем память
NetApiBufferAllocate(1000,bufptr);
//выполняем функцию
x:=NetServerGetInfo(servername, level, bufptr);

if x=NERR_SUCCESS then
begin
//если выполнение успешно, то выполняем присвоение указателей...
myInfo:=bufptr;
//...и выводим в область вывода полученную информацию
lbMain.Items.Add('1. ID платформы: '+IntToStr(myInfo.sv101_platform_id));
lbMain.Items.Add('2. Имя сервера : '+WideCharToString(myInfo.sv101_name));
lbMain.Items.Add('3. Версия :' +IntToStr(myInfo.sv101_version_major)+'.'+IntToStr(myInfo.sv101_version_minor));
lbMain.Items.Add('5. Комментарий:'+WideCharToString(myInfo.sv101_comment));
end;//if ok
//в любом случае освобождаем выделенную память
NetApiBufferFree(bufptr);
end;//prc

procedure TfrmMain.btnCloseClick(Sender: TObject);
begin
Close
end;//prc

end.

Мы идём дальше

А дальше ты видишь блок кода, который вызывается в ответ на нажатие кнопки по имени btnMain. Первым делом объявим константы - тут они объявлены локально, поскольку в этом примере используется только одна функция, а вообще-то ничто не мешает их сделать глобальными. Итак, константа level будет содержать число, указывающее насколько подробный ответ мы хотим получить. Обычно, для любой функции из группы LAN Manager эта константа может принимать несколько значений, мы же возьмём 101, поскольку для выполнения функции на этом уровне нет никаких особых требований к членству в группах домена или рабочей станции(т.е. админом можешь ты не быть, но программистом быть обязан :)). Затем константа NERR_SUCCESS - ничего особенного, просто ноль, однако ноль - не всегда ничто, иногда это признак успеха. Как раз наш случай - если выполнение функции пройдет успешно, то будет возвращен этот самый ноль.

Дальше объявляем переменные: х - в нем будет храниться результат выполнения функции NetServerGetInfo, servername - NetBIOS имя опрашиваемого сервера, длина которого, если мне не изменяет память, не может быть больше 15 символов, bufptr - не типизированный указатель на область памяти с результатом выполнения, myInfo - указатель на запись.

После такой артподготовки весь оставшийся код выглядит яснее ясного. Вот что мы делаем дальше - очищаем окно lbMain от предыдущей информации, конвертируем введённое имя сервера в Unicode-строку, выделяем область памяти для результата, размером "на наше усмотрение", и, наконец, выполняем нашу незабвенную функцию NetServerGetInfo. Далее анализируем результат, т.е. переменную х. Если она равна нулю, то выводим полученную информацию в окно, если нет - не выводим. И в любом случае убираем за собой - т.е. освобождаем выделенную память.

От практики к теории или мы, кажется, притопали

"Ну, вот, - скажешь ты. Столько мучений и все ради чего?" А есть ради чего. Посмотри на функции, портированные из LAN Manager, и у тебя зачешутся руки :). Перечислю тебе только некоторые из них:

подгруппа File
функция NetFileEnum
предоставляет информацию об открытых файлах на сервере*

подгруппа Global group (одна из самых преспективных :))
функция NetGroupAdd
создает на сервере глобальную группу
функция NetGroupAddUser
добавляет к существующей глобальной группе указанного пользователя
функция NetGroupDel
удаляет глобальную группу
функция NetGroupDelUser
удаляет указанного пользователя из глобальной группы
(представь себе картину: заходит админ в сеть, а он уж больше не админ :))
функция NetGroupEnum
перечесляет все глобальные группы
функция NetGroupGetUsers
выдаёт список членов глобальной группы

подгруппа Local group (теже функции, что и для глобальных групп, только с заменой в названии NetGroup на NetLocalGroup)

подгруппа Server
функция NetServerEnum
перечисляет все ПК, видимые в указанном домене, можно указать признак для ПК. Т.е. отдать команду типа "а перечислить мне все ПК домена на которых запущен MS SQL Server". Занятная функция.
функция NetServerDiskEnum
возвращает список дисков на сервере

подгруппа User
функция NetUserAdd
добавляет пользователя, устанавливает пароль и права
функция NetUserEnum
возвращает учетные записи всех пользователей на на указанном сервере
функция NetUserGetGroups
возвращает список глобальных групп, членом которых является указанный пользователь
функция NetUserGetLocalGroups 
возвращает список локальных групп, членом которых является указанный пользователь

Примечание. *В данном контексте и далее "сервер" - опрашиваемый ПК

"Ну да, - снова возмутишься ты. Для того чтобы выполнить самые вкусные из этих функций нужны как минимум права оператора учётных записей, а еще лучше админа." На что я могу тебе напомнить самый простой из способов получения пароля админа. Зачастую в больших конторах, где стоят большие сетки, рабочие станции инсталлируются под эккаунтом админа. Так удобнее. Но вот в чём хитрость - если это Win9x, то она сохранит его логин/пароль в файле с широко известным в народе именем логин.pwl, который обычно забывают удалять. И если где-то в сетке ты найдешь такой файл, а затем и выудишь из него пароль, то самая главная твоя проблема решена. Впрочем и без этого пароля можно узнать много интересного. В качестве примера приведу код, в результате исполнения которого можно получить список локальных групп на опрашиваемом ПК.

Листинг 2. Пример использования функции NetLocalGroupEnum (архив
Lanv.zip
)

procedure TfrmMain.btnLGShowClick(Sender: TObject);
var
//bufptr - нетипизированный указатель на область памяти, заполненную в результате выполнения
bufptr : pointer;
//prefmaxlen - максимальный объем возвращаемых данных в байтах
prefmaxlen,
// entriesread - количество считанных элементов (записей)
entriesread,
// totalentries - всего элементов (записей)
totalentries : DWORD;
//х содержит результат выполнения функции
x : NET_API_STATUS;
// resume_handle - указатель, использующийся для дальгейшего поиска в локальной группе
resume_handle 
:PDWORD;
// указатель на возвращаемую структуру
myLG :PLocalGroupInfo1;
i :integer;
// переменная для хранения NetBIOS имени ПК в формате Unicode
servername :array [0..14] of WideChar;
begin
//конвертируем введенное имя ПК в Unicode-строку
StringToWideChar(cbLG.Text,servername,15);
//выбираем произвольный объем памяти для данных, возвращаемых функцией
prefmaxlen :=15000;
//очистка области вывода
lbLG.Items.Clear;
//обнуляем переменные для повторного использования
entriesread:=0;totalentries:=0;
//выделяем память для возвращаемых данных
NetApiBufferAllocate(15000, bufptr);
//указатель на хэндл существующей локальной группы, при первом вызове должен быть равен 0
new(resume_handle);resume_handle^:=0;
//выполняем функцию
x:=NetLocalGroupEnum(servername, 1, bufptr, prefmaxlen, entriesread, totalentries, resume_handle);
//выполняем присвоение указателей
myLG:=bufptr;
//усли выполнение функции успешно...
if x=NERR_SUCCESS then begin
//...и была хоть одна локальная группа...
if totalentries>0 then 
//...то заполняем область вывода...
lbLG.Items.Add('Группа:'+WideCharToString(myLG^.lgrpi1_name)+';'+'Комментарий:'
+WideCharToString(myLG^.lgrpi1_comment));
//...для всех считанных вхождений
for i:=1 to entriesread-1 do begin
Inc(myLG,1);
lbLG.Items.Add('Группа:'+WideCharToString(myLG^.lgrpi1_name)+';'+'Комментарий:'+
WideCharToString(myLG^.lgrpi1_comment));
end;//for
end;//if

if x<>NERR_SUCCESS then ShowMessage('Ошибка выполнения');
//освобождаем память
NetApiBufferFree(bufptr);
dispose(resume_handle);
end;//prc

И на последок

Как ты уже заметил, между листингами 1 и 2 существует некоторая разница, а точнее во втором листинге отсутствуют объявления внешних функций, а также типов. Тем не менее все заработает, если ты сходишь по адресу
www.delphi-jedi.org и сольёшь оттуда файлик JwaLM.zip, содержащий их, а после разархивирования добавишь в своём проекте в строку uses юнит JwaLM. Скажи спасибо нашим капиталистическим товарищам Petr Vones и Marcel van Brakel со товарищи, которые делали (и продолжают делать) неблагодарное дело конвертации сишных хедеров на Object Pascal. Там же можно найти массу других полезных вещей. Теперь тебя интересует вопрос почему мы не воспользовались трудами этих классиков конвертации сразу же? А как по мне, то лучше один раз увидеть, чем сто раз услышать. А если еще и руками потрогать - то это вообще кайф. А так как кайфовать лучше бандой, то я и написал эту статью в ][, чего и тебе желаю.

ЗЫ. К статье прилагается архив с (LANV.zip) исходниками на Delphi 6 с примерами использования некоторых из описанных выше функций. Если заинтересовался - загляни.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии