Рассылка закрыта
При закрытии подписчики были переданы в рассылку "Delphi - проблемы и решения" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
NetAPI: Работа с учетными записями пользователей.
Информационный Канал Subscribe.Ru |
Программирование для Windows NT на Delphi
Выпуск номер : 1 [ 8 июля 2002 года]
Добрый день.
С прошлого выпуска рассылки пришло только одно письмо. В этот раз подписчиков побольше, поэтому хотелось бы услышать ваши отклики.
Сегодня в рассылке:
- Управление учетными записями пользователей
- FAQ : Посылаем сообщение (как net send)
- Новый взгляд на завершение других процессов
В этом и нескольких последующих выпусках рассылки будет рассказываться о Net API и управлении доверенными объектами - учетными записями пользователей и групп.
Для начала несколько слов о Net API и Delphi.
Во-первых, все функции используют Unicode набор символов. И хотя ходят слухи о всемогущем дельфевом компиляторе, якобы самостоятельно преобразующем обычные (String,PChar) строки в длинные (WideString,PWideChar), но я буду в примерах переконвертировать их самостоятельно (чего и вам советую). Это можно сделать так.
Во-вторых, к сожалению, в стандартной поставке Delphi описаний этих функций нет. Поэтому тут есть два выхода - описать их самим или скачать отсюда - ftp://delphi-jedi.org/api/LanManager.zip [330 Kb]
Все названия функций имеют вид Net[имя объекта][действие]. Например, функция получения информации (GetInfo) о пользователе (User) - NetUserGetInfo.
В Net API для многих функций используются многоуровневые типы данных. Какой уровень надо использовать зависит от детальности запрашиваемой информации и наличия прав. Для примера возьмем NetServerGetInfo. Вторым параметром у нее level : DWORD, а третьим указатель на буфер. Для данной функции возможны 3 уровня - 100,101 и 102. Уровень 100 дает минимум информации о сервере - только имя и тип платформы. Уровень 101 уже побольше. Но больше всех инфы дает уровень 102, но ... эту информацию уже могут получить только пользователи с административными привилегиями. Так что совет - не жадничайте и запрашивайте только тот уровень, который вам необходим. Итак, вторым параметром мы поставим уровень 101 и в третьем получим указатель на какой-то буфер. Но что будет в этом буфере? А будет в нем структура SERVER_INFO_101. Для уровня 102 соответственно будет структура SERVER_INFO_102 и т.д.
Теперь о буфере - буфер выделяется нам системой и она же должна будет его очистить. Так что не забываем использовать, где надо NetApiBufferFree. Выделять буфер самостоятельно не надо (такую ошибку я допустил в одном из примеров в FAQ).
На этом вводная часть закончена, переходим к практике. В этом выпуске работа с учетными записями пользователей.
Статья |
Работа с учетными записями пользователей |
Для начала получим список всех учетных записей пользователей на сервере.(Под словом сервер здесь и далее понимается компьютер - сервер или рабочая станция) Делается это функцией NetUserEnum.
Uses LmAccess, LmCons,LmApiBuf,...; // Это описания нужных функций из архива type PUserArray = ^TUserArray; TUserArray = array of USER_INFO_10; PUSER_INFO_10 = ^USER_INFO_10; procedure EnumUsers; Var I : Integer; pBuffer : Pointer; cbRead,cbTotal : Cardinal; hRes : Cardinal; lpwSrv : PWideChar; Begin UsrList.Clear; // Очищаем TListBox от записей lpwSrv := Str2Wide(Comp.Text); // делаем Unicode копию строки cbRead :=0; //кол-во прочитанных учеток cbTotal := 0; //общее кол-во учеток hRes :=0; //об этом попозже // Получаем список ВСЕХ учетных записей на сервере lpwSrv // pBuffer - указатель на массив элементов типа USER_INFO_* // Cardinal(-1) обозначает ВСЕ учетные записи if NetUserEnum(lpwSrv,10,FILTER_NORMAL_ACCOUNT, pBuffer,Cardinal(-1),cbRead,cbTotal,@hRes)=0 // теперь у нас в cbRead - кол-во элементов // а в cbTotal- их общее кол-во (в данном случае они должны совпадать) then begin for i:=0 to cbRead-1 do // Выводим список пользователей в ListBox UsrList.Items.Add(TUserArray(pBuffer)[i].usri10_name); end; // Очищаем буффер NetApiBufferFree(pBuffer); // Очищаем буффер после Str2Wide FreeMem(lpwSrv); End;и все бы было хорошо, если бы не Cardinal(-1). Если количество учеток не велико, то система выделит небольшой буфер и поместит туда массив структур. Но если у нас несколько тысяч учетных записей, то я бы не рекомендовал использовать эту величину. Вместо этого мы будем получать их по частям. Как раз для этого и используется последний параметр функции.
procedure EnumUsers2; Var I : Integer; pBuffer : Pointer; cbRead,cbTotal : Cardinal; hRes : Cardinal; lpwSrv : PWideChar; Res : Cardinal; Begin UsrList.Clear; lpwSrv := Str2Wide(Comp.Text); cbRead :=0; cbTotal := 0; hRes :=0; repeat Res:=NetUserEnum(lpwSrv,10,FILTER_NORMAL_ACCOUNT, pBuffer,100,cbRead,cbTotal,@hRes); if (Res=0) or (Res=ERROR_MORE_DATA) then begin for i:=0 to cbRead-1 do UsrList.Items.Add(TUserArray(pBuffer)[i].usri10_name); NetApiBufferFree(pBuffer); end; until Res<>ERROR_MORE_DATA; FreeMem(lpwSrv); End;Функция возвращает ERROR_MORE_DATA если еще остались записи, которые не поместились в буфер. И пока они остались, мы будем вызывать функцию NetUserEnum снова и снова. В hRes помещается описатель, который необходим для продолжения перечисления.
Пока мы не перешли к другой функции - Код Str2Wide, переводящей ANSI символы в Unicode.
function Str2Wide(lpStr: String):PWideChar; var dwStrlen : Cardinal; begin dwStrLen := lstrlen(PChar(lpStr)); GetMem(Result,(dwStrLen+1)*2); StringToWideChar(lpStr,Result,dwStrLen+1); end;Не забывайте делать FreeMem!!! Wide := Str2Wide(Str);... FreeMem(Wide);
Теперь создадим учетную запись пользователя функцией NetUserAdd
function AddUser(UserName,Password,Comment:String):boolean; var Size : Cardinal; Res : Cardinal; User : USER_INFO_1; // Уровня 1 вполне достаточно dwErr : Cardinal; lpwSrv : PWideChar; begin ZeroMemory(@User,SizeOf(USER_INFO_1)); //Обнуляем структуру // Это делать не обязательно, если мы заполняем все ее поля. User.usri1_name:=Str2Wide(UserName); User.usri1_password:=Str2Wide(Password); User.usri1_comment:=Str2Wide(Comment); User.usri1_priv:=1; // Уровень привилегий - ДОЛЖЕН БЫТЬ 1 lpwSrv := Str2Wide(Comp.Text); // Имя компьютера Res:= NetUserAdd(nil,1,@User,@dwErr); // Res - код ошибки. Для перевода кода ошибки в строку можно // использовать SysErrorMessage(Res) из SysUtils Result:=(Res=0); end;Ну и осталось научиться получать информацию о пользователях и менять ее. NetUserGetInfo получает информацию о аккаунте пользователя.
procedure GetUserInfo(Server,User: String); var UserInfo : ^USER_INFO_2; lpwUser : PWideChar; lpwSrv : PWideChar; begin lpwSrv := Str2Wide(Server); lpwUser := Str2Wide(User); NetUserGetInfo(lpwSrv,lpwUser,2,Pointer(UserInfo)); // Куда - то выводим информацию // При желании изменяем и сохраняем новую инфу NetUserSetInfo(nil,lpwUser,2,UserInfo,nil); NetApiBufferFree(UserInfo); end;Из всех уровней самый интересный, имхо, второй. (Правда туда пускают только админов, а неадминов пускают на 0 уровень, не содержащий ничего полезного). Вот его то мы и разберем.
usri2_name - имя пользователя - это думаю понятно
usri2_password - пароль - всех надеющихся увидеть здесь пароль огорчу - здесь его нет и не будет (поле используется только при установке информации)(W)
usri2_password_age - "возраст" пароля - кол-во секунд, прошедших с момента последней смены пароля (R)
usri2_priv - уровень привилегий - 1 - обычный пользователь, 0 - гость, 2 - админ (определяется группами, в которых находится пользователь) (R)
usri2_home_dir - основной каталог пользователя - обычно используется для подключения сетевого каталога пользователя (например, \\server\users\thisuser)
usri2_comment - описание
usri2_flags - флаги состояния аккаунта -
UF_ACCOUNTDISABLE = $0002 - отключена
UF_PASSWD_CANT_CHANGE = $0040 - пароль не может быть сменен пользователем
UF_DONT_EXPIRE_PASSWD = $10000 - срок действия пароля не ограничен
usri2_script_path - скрипт, выполняемый при входе в систему
usri2_auth_flags - административные флаги - какие привилегии имеет пользователь (R)
AF_OP_PRINT = $1; // Print Operator
AF_OP_COMM = $2; // Communications Operator
AF_OP_SERVER = $4; // Server Operator
AF_OP_ACCOUNTS= $8; // Account Operator
usri2_full_name - полное имя пользователя
usri2_usr_comment - пользовательский комментарий
usri2_workstations - до восьми компьютеров, на которых может работать пользователь
usri2_last_logon и usri2_last_logoff - время последнего входа и выхода пользователя (R)
usri2_acct_expires - время (кол-во секунд с 1.01.1970 года) истечения срока действия пароля
usri2_max_storage - макс. размер дискового пространства, который может использовать юзер
usri2_units_per_week и usri2_logon_hours определяют разрешенное время входа
usri2_bad_pw_count - кол-во вводов неправильного пароля (R)
usri2_num_logons - кол-во входов в систему (R)
usri2_logon_server - имя сервера, обрабатывающего вход в систему.
usri2_country_code и usri2_code_page - коды региона и кодовой страницы языка.
(R) и (W) - поля только для чтения и только для "записи".
Устанавливается информация функцией NetUserSetInfo.
NetUserSetInfo(nil,lpwUser,2,UserInfo,nil);
UserInfo - указатель на структуру User_Info_2 полученную функцией NetUserGetInfo.
В принципе, все поля устанавливаются очень просто, кроме usri2_logon_hours.
Что же это за поле? Это 168 битная строка, каждый бит которой - час недели. Первый бит - с 000 до 059 Воскресения, Второй с 100 до 159 и т.д. и все это для нулевого часового пояса (GMT). Для 3 часового пояса нулевой бит - время с 300 до 359.
Реализуем процедуру, разрешающую вход в определенный час
type TLogonHours = array [0..20] of Byte; procedure SetHour(hour,day : Byte;var mas : TLogonHours); var ByteNum: Cardinal; dwHour : Integer; begin // Вычисляем номер часа в неделе по GMT //TimeZone - часовой пояс (разница времени по Гринвичу (GTM) и в вашей местности) dwHour := hour+24*day-TimeZone; if dwHour<0 then dwHour := 168+dwHour; ByteNum:= dwHour div 8; // Номер байта в массиве // Устанавливаем соответствующий бит в байте mas[bytenum] := mas[bytenum] or (1 shl (dwHour mod 8)); end; var hours : TLogonHours; SetHour(12,1); // разрешаем вход в пн. с 12 до 13 UserInfo.usri2_logon_hours:=@hours;И еще - проверить включен определенный флаг или нет можно так
// Заблокирован ли юзер (UF_ACCOUNTDISABLE = $0002) Result:= ((UserInfo.usri2_flags and UF_ACCOUNTDISABLE)=0);
Вопросы и ответы |
Как послать сообщение пользователю или на другой компьютер (как net send ..) ? |
Странно, но этот вопрос оказался самым популярным в FAQ (а раньше самым популярным был про перезагрузку :)), поэтому я его публикую здесь. Кстати, вопрос тоже на тему NetAPI.
Function NetMessageBufferSend ( servername: LPCWSTR; msgname: LPCWSTR; fromname: LPCWSTR; buf: Pointer; buflen: DWORD ): longint; stdcall; external 'netapi32.dll'; Implementation // процедура посылки сообщения Procedure SendLanMessage; Var HostName: LPCWSTR; RName: LPCWSTR; buf: Pointer; text: String; size: Integer; Begin // Посылаем сообщение с локального компьютера для User фразу // "We are testing lan manager" HostName := Nil; RName := 'User'; text := 'We are testing lan manager'; // Перевод String в WideString size := SizeOf (WideChar) * Length (text)+1; GetMem (buf, Size); PWideChar (Buf) := StringToWideChar (text, buf, Length (text) + 1); If NetMessageBufferSend (HostName, RName, Nil, buf, size) <> NO_ERROR then ShowMessage ('Error'); End;
Напоследок |
Новый взгляд на завершение других процессов |
Интересный способ завершения других процессов, которым поделился Ворническу Владимир (за что ему огромное спасибо).
Обычно процесс завершают TerminateProcess'ом. При этом, как рассказывается в MSDN, пользоваться им стоит только в экстренных случаях, т.к. DLL'ки используемые процессом, не получают уведомления о завершении процесса :(. А вот при ExitProcess все нормально :) Ну вот и будем использовать ExitProcess...
function NTKillProcess(iProcessID: Integer): Integer; var hProcess: Integer; pfnExitProcess: Pointer; hInstance: Integer; hThread: DWORD; begin Result:=ERROR_SUCCESS; hProcess:=OpenProcess(PROCESS_ALL_ACCESS, FALSE, iProcessID); if hProcess > 0 then begin hInstance:=GetModuleHandle('KERNEL32.DLL'); pfnExitProcess:=GetProcAddress(hInstance, 'ExitProcess'); hThread:=CreateRemoteThread(hProcess, nil, 0, pfnExitProcess, nil, 0,hThread); if hThread>0 then begin WaitForSingleObject(hThread, 40000); if not CloseHandle(hThread) then Result:=GetLastError; end else Result:=GetLastError; CloseHandle(hProcess); end else Result:=GetLastError; end;Добавлю, что работать, скорее всего, будет только для Win32 процессов (а другие редко бывают).
На сегодня это все. Очень бы хотелось попросить вас писать мне на flint@xonix.ru если увидите ошибку, хотите поделиться информацией или задать вопрос, высказать мнение о рассылке ... в общем, по любому поводу.
http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
В избранное | ||