Программирование для Windows NT на Delphi Выпуск номер : 2 [ 15 июля 2002 года]
Здравствуйте, уважаемые подписчики.
У меня к Вам такой вопрос - в следующем выпуске я закончу тему NetAPI и вопрос -
о чем писать дальше? Может сообщите, какие темы вам интересны ? ( О чем писать я конечно
найду, но хотелось бы, чтобы это было интересно Вам). Также со следующего выпуска
начну публиковать некоторые ваши вопросы (задать которые вы можете,
написав мне)
Сегодня я продолжу тему NetAPI : как работать с группами пользователей.
Для начала о группах - служат они, как известно, для упрощения администрирования :
вместо того, чтобы устанавливать права доступа, привилегии и т.д. каждому пользователю,
их (пользователей) можно объединить в группы и настраивать права уже на уровне групп.
Тем более, что для системы установка прав, как для групп, так и для пользователей
практически одинакова.
Как известно, группы бываут локальные ("работают" только на одном
(локальном) компьютере) и глобальные (для всего домена, если такой имеется). С точки
зрения API работа с ними отличается. И хотя функции совпадают практически до параметров,
но в примеры я буду давать для обоих типов.
Для работы с локальными группами используются функции NetLocalGroup*, а с глобальными
NetGroup*.
Статья
Учетные записи групп пользователей
Начнем с перечисления групп. Пример будет даваться для обоих типов групп сразу,
поэтому следите за комментариями :)
type
// Локальные группы
TLocalGroups = array of LOCALGROUP_INFO_1;
// Глобальные
TGroups = array of GROUP_INFO_1;
var
pGroups : Pointer;
Res, i : Cardinal;
cbread,cbTotal : Cardinal;
begin
ListBox1.Clear;
// Опять - первая локальная, вторая глобальная
Res:= NetLocalGroupEnum(lpwSrv,1,pGroups,Cardinal(-1),cbRead,cbTotal,nil);
Res:= NetGroupEnum(lpwSrv,1,pGroups,Cardinal(-1),cbRead,cbTotal,nil);
For i:=0 to cbRead-1 do
// Вывод групп в ListBox (как и выше )
ListBox1.Items.Add(TLocalGroups(pGroups)[i].lgrpi1_name);
ListBox1.Items.Add(TGroups(pGroups)[i].grpi1_name);
// Очищаем буфер - Каждый удачный вызов Net*GroupEnum должен
// сопровождаться вызовом NetApiBufferFree
NetApiBufferFree(pGroups);
end;
Все зависит от задачи, но , как вы понимаете из этих пар строк должна остаться только одна нужная.
lpwSrv - Имя сервера (PWideChar). cbRead - кол-во прочитанных групп, а cbTotal -
их общее кол-во. В данном случае они должны совпадать, т.к. макс. кол-во перечисляемых
групп не ограничено (4 параметром используется Cardinal(-1) ). Не ограничено оно в
этом примере по двум причинам - размер рассылки (если вам очень интересно как перечислять
группы "по частям", загляните в прошлый номер рассылки - там был подобный пример, но для
перечисления пользователей). Второй причиной является то, что встретить сервер с несколькими
тысячами групп практически невозможно (а вот с пользователями запросто).
И еще - проверяйте возвращаемое функцией значение. В случае удачи оно должно быть 0 и
только 0.
Теперь создадим группу - локальную и глобальную.
var
lgi1 : _LOCALGROUP_INFO_1;
Res : Cardinal;
begin
lgi1.lgrpi1_name:=lpwGrpName; //Имя группы
lgi1.lgrpi1_comment:='Group Comment'; // Комментарий
Res:= NetLocalGroupAdd(lpwSrv,1,@lgi1,nil);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
var
gi : GROUP_INFO_1;
Res : Cardinal;
begin
gi.grpi1_name:=lpwGrpName;
gi.grpi1_comment:='Commentariy';
Res:= NetGroupAdd(lpwSrv,1,@gi,nil);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
Удалять группы еще проще -
var
Res : Cardinal;
begin
// локальную
Res:= NetLocalGroupDel(lpwSrv,lpwGrpName);
// или глобальную
Res:= NetGroupDel(lpwSrv,lpwGrpName);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
Следующая пара функций - получение информации о группе.
Для глобальных групп поддерживается 4 уровня. Уровень 0 дает нам только имя группы
(интересно, зачем оно нам :), 2ой - кроме имени и комментария не дает ничего полезного -
атрибуты жестко зафиксированы, а id как-то не очень нужен (вот если бы SID группы..., но
он доступен только на 3 уровне, поддерживаемом только XPшкой). Поэтому как обычно будем
пользоваться 1 уровнем.
// Получаем комментарий для локальной группы lpwGrpName на компьютере lpwSrv
var
Buf : ^LOCALGROUP_INFO_1;
begin
if NetLocalGroupGetInfo(lpwSrv,lpwGrpName,1,Pointer(Buf))=0
then begin
ShowMessage(Buf.lgrpi1_comment);
NetApiBufferFree(Buf);
end;
end;
// И то же самое для глобальной
var
Buf : ^GROUP_INFO_1;
begin
if NetGroupGetInfo(lpwSrv,lpwGrpName,1,Pointer(Buf))=0
then begin
ShowMessage(Buf.grpi1_comment);
NetApiBufferFree(Buf);
end;
end;
Ну и последнее - установка свойств групп, а именно комментария. Остальные (имя группы) параметры
LOCALGROUP_INFO_1 и GROUP_INFO_1 игнорируются.
var
LGI : LOCALGROUP_INFO_1;
Res : Cardinal;
begin
// новый коментарий локальной группы
LGI.lgrpi1_comment:='NewComment';
Res:=NetLocalGroupSetInfo(lpwSrv,lpwGrpName,1,@LGI,nil);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
var
GI : GROUP_INFO_1;
Res : Cardinal;
begin
// новый коментарий глобальной группы
GI.grpi1_comment:='NewComment';
Res:=NetGroupSetInfo(lpwSrv,lpwGrpName,1,@GI,nil);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
О работе с группами это все. Теперь немного о пользователях и группах - перемещении,
удалении, добавлении в группы.
// Добавляем в глобальную группу lpwGrpName юзера lpwUser
var
Res : Cardinal;
begin
Res:=NetGroupAddUser(nil,lpwGrpName,lpwUser);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
// Удаляем из глобальной группы lpwGrpName юзера lpwUser
var
Res : Cardinal;
begin
Res:=NetGroupDelUser(nil,lpwGrpName,lpwUser);
if Res<>ERROR_SUCCESS then ShowMessage(SysErrorMessage(Res));
end;
Продолжение (а точнее окончание) будет в следующем выпуске.
Вопросы и ответы
Как получить список привилегий, доступных программе (пользователю) ?
procedure ShowEnabledPrivileges;
const
TokenSize=800; // размер выделяемой памяти (SizeOf(Pointer)=4 *200)
var
hToken:THandle;
pTokenInfo:PTOKENPRIVILEGES;
ReturnLen:Cardinal;
i:Integer;
PrivName:PChar;
DisplayName:PChar;
NameSize:Cardinal;
DisplSize:Cardinal;
LangId:Cardinal;
begin
// Выделяем память под массив
GetMem(pTokenInfo,TokenSize);
// Получаем токен нашего процесса
if not OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken)
then ShowMessage('OpenProcessToken error');
// Получаем информацию о привилегиях процесса
if not GetTokenInformation(hToken,TokenPrivileges,
pTokenInfo,TokenSize,ReturnLen)
then ShowMessage('GetTokenInformation error');
GetMem(PrivName,255);
GetMem(DisplayName,255);
for i:=0 to pTokenInfo.PrivilegeCount-1 do
begin
DisplSize:=255;
NameSize:=255;
// получаем название и имя каждой из PrivilegeCount-1 привилегий
LookupPrivilegeName(nil,pTokenInfo.Privileges[i].Luid,PrivName,Namesize);
LookupPrivilegeDisplayName(nil,PrivName,DisplayName,DisplSize,LangId);
// выводим их в ListBox1 типа TListBox (свойства: Columns>=2; TabWidth>0)
ListBox1.Items.Add(PrivName+^I+DisplayName);
end;
FreeMem(PrivName);
FreeMem(DisplayName);
FreeMem(pTokenInfo);
end;
Напоследок
Прячем файлы с настройками
Предположим у вашей программы есть файл с настройками, в который желательно разрешить
писать только вашей программе. Допустим, при запуске программы вы читаете настройки
из этого файла, и при закрытии записываете новые. Но как сделать, чтобы доступ к настройкам
был только у вашей программы и пользователи не правили его вручную?
Прежде чем описать его скажу, что он несколько экзотичен, работоспособен только на
текущих версиях НТ (что ждать от следующего сервис-пака не известно), проверялся мною
(а ссылку на оригинал информации я к сожалению утратил) на NTFS (а скорее всего только там и работает)
и вероятно не защитит он advanced users с различными FileMon'ами.
Итак, что будет если мы создадим каталог, полный путь к которому будет больше, чем
MAX_PATH = 260 символов? А будет то, что мы не получим доступа к его содержимому.
Но сможем создать такой каталог обычным путем. Попробуем обходным...
Создаем каталог,полный путь которого (C:\test\много символов\) будет около 260 символов.
Теперь пытаемся создать в этом каталоге подкаталог, полный путь которого
(C:\test\много символов\catalog)будет больше 260 ... Вероятно не получилось.
А если сделать subst, т.е. сопоставить нашему C:\test\много символов\ букву диска.
А теперь создать в нем каталог h:\catalog = C:\test\много символов\catalog и записать
в нем наш файлик.
Теперь доступ к файлу возможен только через "виртуальный" диск, а через реальный путь нет.
Теперь в начале и в конце работы программы назначаем нашему каталогу букву диска, читаем
файлик и "отключаем" диск.
Выполнить сопоставление можно или программой subst или программно.
//За пример благодарим Ворническу Владимира
procedure addSubst(Drv: string; Path: string);
begin
if (Length(Path) = 0) then Exit;
if (DefineDosDevice(0, PChar(Drv), PChar(Path)) = False)
then RaiseLastWin32Error;
end;
procedure KillSubst(Drv: string);
begin
if (DefineDosDevice(DDD_REMOVE_DEFINITION, PChar(Drv), nil) = False)
then RaiseLastWin32Error;
end;
addSubst('h:','c:\....\');
потом читаем наш файлик из подкаталога и
KillSubst('h:');