Программирование для Windows NT на Delphi Выпуск номер : 3 [ 23 июля 2002 года]
Здравствуйте, уважаемые подписчики.
Сначала о хорошем - рассылку перевели в категорию "Обычные", с чем я себя и вас поздравляю.
Теперь о плохом - об ошибке, в выпуске за 8 июля.
[...]
в функции AddUser допущена ошибка, вместо строк
lpwSrv := Str2Wide(Comp.Text); // Имя компьютера
Res:= NetUserAdd(nil,1,@User,@dwErr);
должно быть
lpwSrv := Str2Wide(Comp.Text); // Имя компьютера
Res:= NetUserAdd(lpwSrv,1,@User,@dwErr);
(то есть в качестве имени сервера передаем имя, а не nil)
С уважением, Дмитрий Заузолков
Спасибо Дмитрию за замечание. Ошибка получилась из за того, что в исходниках было или nil или имя сервера, и переделывалось все уже при подготовке рассылки. Пришу извенить за подобные ошибки.
Статья
Управление пользователями в группах
В прошлом выпуске мы уже затронули две функции для работы с пользователями в группах - NetGroupAddUser и NetGroupDelUser (для добавления и удаления пользователя в глобальную группу). Третья функция NetGroupGetUsers получает список пользователей глобальной группы.
В параметрах функций - lpwServer - сервер, на котором находится группа lpwGroup. lpwUser - соответственно пользователь.
uses LmApiBuf,LmAccess;
procedure EnumGroupUsers(lpwServer,lpwGroup: PWideChar);
type
TUserGroupArray = array of GROUP_USERS_INFO_0;
const
// Размер выделяемого буфера
PREF_LEN = 1024;
var
pBuffer:Pointer; // Указатель на наш буфер
Res,dwRead,dwTotal,hRes,i:Cardinal;
begin
hRes:=0;
ListBox1.Clear; //Будем выводить результат в ListBox, а пока очищаем его
repeat
// Перечисляем пользователей группы lpwGroup на компютере lpwServer
Res:=NetGroupGetUsers(lpwServer,lpwGroup,0,pBuffer,PREF_LEN,dwRead,dwTotal,@hRes);
//В случае удачи результат может быть или ERROR_SUCCESS (все данные получены)
// или ERROR_MORE_DATA - будем еще перечислять
if (Res=0) or (Res=ERROR_MORE_DATA)
then begin
for i:=0 to dwRead-1 do
// Вывод списка
ListBox1.Items.Add(TUserGroupArray(pBuffer)[i].grui0_name);
NetApiBufferFree(pBuffer);
end;
until Res<>ERROR_MORE_DATA;
end;
Теперь узнаем, в каких группах находится определенный пользователь. Для этого можно перечислить всех пользователей во всех группах, и найти те, в которых есть нужный нам пользователь (видел я такую реализацию :)) Но мы воспользуемся функциями NetUserGetGroups и NetUserGetLocalGroups.
procedure GetUserGroups(lpwServer,lpwUser : PWideChar);
type
TUserGroupArray = array of GROUP_USERS_INFO_0;
var
Res : Cardinal;
pBuffer : Pointer;
dwRead,dwTotal,i:Cardinal;
begin
Res:=NetUserGetGroups(lpwServer,lpwUser,0,pBuffer,Cardinal(-1),dwRead,dwTotal);
if Res<>0 then Exit;
for i:=0 to dwRead-1 do
ListBox1.Items.Add(TUserGroupArray(pBuffer)[i].grui0_name);
NetApiBufferFree(pBuffer);
end;
Ну, тут все просто и понятно - получили массив групп и их кол-во, вывели их и очистили буфер.
Это мы получили список глобальных групп, в которых находится пользователь. Теперь получим локальные
procedure GetUserLocalGroups(lpwServer,lpwUser : PWideChar);
type
TLocalGroupArray = array of LOCALGROUP_USERS_INFO_0;
var
pBuffer : Pointer;
cbRead,cbTotal,Res,i : Cardinal;
begin
Res:= NetUserGetLocalGroups(lpwServer,lpwUser,0,LG_INCLUDE_INDIRECT ,
pBuffer,Cardinal(-1),cbRead,cbTotal);
if Res<>0 then Exit;
ListBox1.Clear;
for i:=0 to cbRead-1 do
ListBox1.Items.Add(TLocalGroupArray(pBuffer)[i].lgrui0_name);
NetApiBufferFree(pBuffer);
end;
Эта функция очень похожа на NetUserGetGroups, за исключением 4 параметра dwFlag. Пока определено только одно значение - LG_INCLUDE_INDIRECT, позволяющее выводить те группы, в которых пользователь явно не находится.
Например, пользователь User находится в локальной группе Users и глобальной GlobalUsers. В локальную группу Administrators входит и глобальная группа GlobalUsers. Если параметр равен LG_INCLUDE_INDIRECT, то функция вернет не только группу Users, но и группу Administrators, в которой пользователь явно не указан. А если этот параметр равен нулю... в общем, на NT4Server эта программа работать не захотела :))
Осталось посмотреть работу с пользователями локальных групп. Сначала добавление или удаление
пользователей и группы.
procedure AddDelUsersToGroup(lpwServer,lpwGroup : PWideChar);
type
TGroupInfoArray = array of LOCALGROUP_MEMBERS_INFO_0;
var
pBuffer : TGroupInfoArray;
Res : Cardinal;
Users : TStringList;
i : Integer;
begin
// Создаем TStringList (можно было его позаимствовать у другого класса)
// И добавляем имена пользователей, которых мы добавим в группу
Users:=TStringList.Create;
Users.Add('user1');
users.Add('user2');
Users.Add('User3');
// В этом примере у нас pBuffer (по определению) не указатель, а массив
// Назначаем его новый размер
SetLength(pBuffer,Users.Count);
// Получаем SID по имени пользователя
// Функция GetUserSid будет обсуждаться ниже
For i:=0 to Users.Count-1 do
begin
GetUserSid(lpwServer,Users.Strings[i],pBuffer[i].lgrmi0_sid);
end;
// Добавляем пользователей
Res:= NetLocalGroupAddMembers(lpwServer,lpwGroup,0,pBuffer,Users.Count);
// ИЛИ удаляем их
Res:= NetLocalGroupDelMembers(lpwServer,lpwGroup,0,pBuffer,Users.Count);
ShowMessage(SysErrorMessage(Res));
// Очищаем занятую функцией GetUserSid память
For i:=0 to Users.Count-1 do
FreeMem(pBuffer[i].lgrmi0_sid);
Users.Free;
end;
Функции NetLocalGroup* в отличие от NetGroup* работают не с именами пользователей, а с их SID (подробнее о SID - идентификаторе безопасности, в следующем выпуске). И добавляет/удаляет не одного (в общем случае конечно :)), а несколько пользователей сразу. Для простоты и "приближения к реальным условиям" использовался TStringList. Но можно было взять и TStrings из TListBox или другого компонента. После установки размера массива pBuffer мы по очереди получили SID пользователей из списка. Функции GetUserSid в WinAPI нет, поэтому пришлось написать ее самому.
procedure GetUserSid(lpwServer: PWideChar; sUser:String; var pSid:PSID);
var
lpDomain : PWideChar;
cbDomain,cbSid : Cardinal;
lpwUser : PWideChar;
cbUser : Cardinal;
peUse : Cardinal;
begin
// Переводим имя пользователя в Unicode
cbUser := Length(sUser);
cbUser:=(cbUser+1)*2;
GetMem(lpwUser,cbUser);
StringToWideChar(sUser,lpwUser,cbUser);
cbSid:=0;
cbDomain:=0;
if not LookupAccountNameW(lpwServer,lpwUser,nil,cbSid,nil,cbDomain,
peUse) and (GetLastError=122)
then begin
GetMem(pSid,cbSid);
GetMem(lpDomain,cbDomain*2);
if not LookupAccountNameW(lpwServer,lpwUser,pSid,cbSid,lpDomain,cbDomain,peUse)
then ShowMessage('LookupAccountNameW'#13#10+SysErrorMessage(GetLastError));
FreeMem(lpDomain);
FreeMem(lpwUser);
end;
end;
Что мы тут делали - первый вызов LookupAccountNameW дает нам размер буфера, необходимый для получения SID пользователя. После этого мы выделяем память под SID и имя домена и снова вызываем LookupAccountNameW. Теперь у нас в pSid должен быть указатель на SID пользователя. Имя пользователя мы переводим из String в PWideChar. В конце функции мы очищаем всю занятую нами память, кроме памяти под SID (но не забываем про нее).
Теперь вернемся снова к предыдущему примеру - в GetUserSid мы передаем по очереди все элементы массива pBuffer. После выполнения цикла у нас будет массив указателей на SIDы пользователей. И теперь мы передаем этот массив (вообще то надо указатель на массив), в NetLocalGroupAddMembers или NetLocalGroupDelMembers.
Теперь очищаем занятую нами в GetUserSid память.
Для работы с локальными группами есть еще две функции NetLocalGroupGetMembers и NetLocalGroupSetMembers. Первая из них перечисляет всех пользователей локальной группы.
procedure GetLocalGroupUsers(lpwServer,lpwGroup : PWideChar);
type
TUserArray = array of LOCALGROUP_MEMBERS_INFO_1;
var
pBuffer : Pointer;
cbRead,cbTotal,Res,hRes : Cardinal;
i:Integer;
begin
hRes:=0;
repeat
Res:=NetLocalGroupGetMembers(lpwServer,lpwGroup,1,pBuffer,1024,cbRead,cbTotal,@hRes);
if (Res=0) or (Res=ERROR_MORE_DATA)
then begin
for i:=0 to cbRead-1 do
ListBox1.Items.Add(TUserArray(pBuffer)[i].lgrmi1_name);
NetApiBufferFree(pBuffer);
end;
until Res<>ERROR_MORE_DATA;
end;
Думаю комментировать действия нет смысла (подобные примеры уже встречались не раз). На уровне 1 функция возвращает не только SID пользователя, но и имя. Поэтому воспользуемся этим уровнем. (на уровне 2 - имя пользователя и домена)
Назначение функции NetLocalGroupSetMembers - назначать пользователей, входящих в группу (не добавлять, а назначать - т.е. остальные пользователи из группы удаляются). Я придумал всего два применения этой функции. Первое - скопировать пользователей из одной группы в другую (можно на разных компьютерах).
procedure CopyGroupMembers(lpwServer1,lpwGroup1,lpwServer2,lpwGroup2 : PWideChar);
var
pBuffer : Pointer;
cbRead,cbTotal : Cardinal;
begin
if NetLocalGroupGetMembers(lpwServer1,lpwGroup1,0,pBuffer,Cardinal(-1),
cbRead,cbTotal,nil)=0
then NetLocalGroupSetMembers(lpwServer2,lpwGroup2,0,pBuffer,cbRead);
end;
Тут все просто - получаем пользователей одной группы и устанавливаем их в другой.
Второй пример еще проще - удалим всех пользователей из группы
procedure ClearGroup(lpwServer,lpwGroup : PWideChar);
begin
NetLocalGroupSetMembers(lpwServer,lpwGroup,0,nil,0)
end;
Еще раз напомню - все строки в Net функциях только в Unicode (как перевести String в Unicode можно посмотреть в начале функции GetUserSid). Заголовочные файлы можно скачать отсюда [300Kb].
Вот на этом тема пользователей и групп пока закрыта. Все вопросы пишите мне на flint@vtc.ru
Вопросы и ответы
Работа в контексте залогиненого пользователя из сервиса
Пример позволяет выполнять действия (из сервиса) под пользователем, запустившим процесс с ProcessID = dwPid
function GetUserContext(dwPid : Cardinal):Boolean;
var
hProc : THandle;
hToken : THandle;
User : array [0..100] of Char;
cbUser : Cardinal;
begin
Result:=false;
hProc:=OpenProcess(PROCESS_ALL_ACCESS,false,dwPid);
if hProc = 0 then Exit;
if not OpenProcessToken(hProc,TOKEN_DUPLICATE or TOKEN_QUERY ,hToken)
then Exit;
if not ImpersonateLoggedOnUser(hToken)
then Exit;
// С этого момента код будет выполняться в
// Контексте пользователя, под которым запущен
// процесс c PID = dwPID
cbUser:=100;
GetUserName(User,cbUser);
ShowMessage(User);
RevertToSelf;
// Теперь снова работаем под системой
cbUser:=100;
GetUserName(User,cbUser);
ShowMessage(User);
Result:=true;
end;