Задача: при логине пользователя сервис должен запустить некий процесс с его привилегиями.
1a. Как, собственно, сервис может отловить этот момент? В том числе и для случая Terminal Services или нескольких пользователей для WinXP?
1b. Соответственно, если сервис зпускается уже при залогиненном пользователе, надо как-то его (их) идентифицировать.
2. Как запустить процесс от имени залогиневшегося юзера? Если у того уже запущены какие-то процессы, то последовательность OpenProcess(какой-нибудьтам) + OpenProcessToken() + CreateProcessAsUser() решает проблему, но при приходе какой-нибудь там нотификации может еще ни шелл, ничего другого не быть запущенным...
Здравствуйте, Cavaler, Вы писали:
C>Задача: при логине пользователя сервис должен запустить некий процесс с его привилегиями.
C>1a. Как, собственно, сервис может отловить этот момент? В том числе и для случая Terminal Services или нескольких пользователей для WinXP?
Самый корректный способ, имхо использовать Winlogon Notification Package — в соответствующей процедуре просто бросать event, а в сервисе его ждать. Для того, чтобы работало на Terminal Services, имя event должно начинаться с префикса 'Global\'.
C>1b. Соответственно, если сервис зпускается уже при залогиненном пользователе, надо как-то его (их) идентифицировать.
GetUserName()
C>2. Как запустить процесс от имени залогиневшегося юзера? Если у того уже запущены какие-то процессы, то последовательность OpenProcess(какой-нибудьтам) + OpenProcessToken() + CreateProcessAsUser() решает проблему, но при приходе какой-нибудь там нотификации может еще ни шелл, ничего другого не быть запущенным...
хм, вот тут не очень понял. Если нотификация о лигоне пришла, то пользователь уже залогинен. При чем тут шелл неясно...
Здравствуйте, Conr, Вы писали:
Ca>>1a. Как, собственно, сервис может отловить этот момент? В том числе и для случая Terminal Services или нескольких пользователей для WinXP? Cr>Самый корректный способ, имхо использовать Winlogon Notification Package — в соответствующей процедуре просто бросать event, а в сервисе его ждать.
Ага, да, нашел такое. Слегка смущает Win2000+ — ну да я давно уже NT4 не видел.
Единственная неприятность — кроме собственно факта чьего-то логина, хочется еще и User Token передать. А что-то до способа просто и изящно передать маленький блок данных от одного процесса другому я так и не придумал (плюс чтобы еще можно было его ждать так же, как евент или там семафор какой-нибудь), хотя наверняка он есть.
Ca>>1b. Соответственно, если сервис запускается уже при залогиненном пользователе, надо как-то его (их) идентифицировать. Cr>GetUserName()
Не в этом смысле идентифицировать. А, например, получить User Token для всех залогиненных (see below).
Ca>>2. Как запустить процесс от имени залогиневшегося юзера? Если у того уже запущены какие-то процессы, то последовательность OpenProcess(какой-нибудьтам) + OpenProcessToken() + CreateProcessAsUser() решает проблему, но при приходе какой-нибудь там нотификации может еще ни шелл, ничего другого не быть запущенным... Cr>хм, вот тут не очень понял. Если нотификация о лигоне пришла, то пользователь уже залогинен. При чем тут шелл неясно...
При том, что чтобы запустить процесс от имени юзера, надо иметь User Token. А способ получить, не зная пароля (для LogonUser()) — я нашел только через вышеупомянутую связку OpenProcess()+OpenProcessToken(), натравливая ее на какой-то процесс явно от имени этого юзера — например, шелл.
Собственно, для варианта, предложенного тобой, это не нужно — там этот token получается от winlogon. Но остается вопрос сервиса, запускающегося уже при имеющихся пользователях.
Здравствуйте, Cavaler, Вы писали:
C>Задача: при логине пользователя сервис должен запустить некий процесс с его привилегиями.
на win2k — windows notification package
на xp — see Terminal Service Function (WTSXXX)
C>1a. Как, собственно, сервис может отловить этот момент? В том числе и для случая Terminal Services или нескольких пользователей для WinXP?
WinXP == Terminal Services, только терминалы не удаленный (т.е. Console)
Проверять состояние терминальных сессий. искать WTSActive
C>1b. Соответственно, если сервис зпускается уже при залогиненном пользователе, надо как-то его (их) идентифицировать.
НА win2k с winlogona — пользователь один, проблем не будет. Я передаю данные через Shared Data в dll.
C>2. Как запустить процесс от имени залогиневшегося юзера? Если у того уже запущены какие-то процессы, то последовательность OpenProcess(какой-нибудьтам) + OpenProcessToken() + CreateProcessAsUser() решает проблему, но при приходе какой-нибудь там нотификации может еще ни шелл, ничего другого не быть запущенным...
На терминалах (XP в том числе) через WTSQuerySessionInformation легко получить токен пользователя на указанной терминальной сесии + CreateProcessAsUser (также надо указать рабочий стол Winsta0/Default или свой)
На 2k я запускаю из сервиса приложение через CreateProcess() с правами system ((((((((((
токен можно получить с winlogon'a (в параметрах функции передается стректура, содержащая указатель на токен), но вот уже вторую неделю руки не доходят ибо с первого раза не получилось. Хотя и сделать надо.
И ище — никогда не используй интерактивность в сервисах.
Завтра кину код, если надо.
Здравствуйте, Cavaler, Вы писали:
C>При том, что чтобы запустить процесс от имени юзера, надо иметь User Token. А способ получить, не зная пароля (для LogonUser()) — я нашел только через вышеупомянутую связку OpenProcess()+OpenProcessToken(), натравливая ее на какой-то процесс явно от имени этого юзера — например, шелл. C>Собственно, для варианта, предложенного тобой, это не нужно — там этот token получается от winlogon. Но остается вопрос сервиса, запускающегося уже при имеющихся пользователях.
Токен можно достать из registry hives — для любого залогиненого пользователя хайв уже подгружен.
Здравствуйте, acronim, Вы писали:
C>>1b. Соответственно, если сервис зпускается уже при залогиненном пользователе, надо как-то его (их) идентифицировать. A>НА win2k с winlogona — пользователь один, проблем не будет. Я передаю данные через Shared Data в dll.
Так вот как определить, кто-то уже залогинен или нет? И если да, то user token получить? С Terminal Services хоть как-то понятно, а обычная Win2K? Всякие WTS* хоть на ней и определены, но не работают.
Здравствуйте, Conr, Вы писали:
C>>При том, что чтобы запустить процесс от имени юзера, надо иметь User Token. А способ получить, не зная пароля (для LogonUser()) — я нашел только через вышеупомянутую связку OpenProcess()+OpenProcessToken(), натравливая ее на какой-то процесс явно от имени этого юзера — например, шелл. C>>Собственно, для варианта, предложенного тобой, это не нужно — там этот token получается от winlogon. Но остается вопрос сервиса, запускающегося уже при имеющихся пользователях. C>Токен можно достать из registry hives — для любого залогиненого пользователя хайв уже подгружен.
А где его там брать? И, опять же, что, просто перебирать все ключи в HKEY_USERS?
Здравствуйте, Cavaler, Вы писали:
C>Здравствуйте, acronim, Вы писали:
C>>>1b. Соответственно, если сервис зпускается уже при залогиненном пользователе, надо как-то его (их) идентифицировать. A>>НА win2k с winlogona — пользователь один, проблем не будет. Я передаю данные через Shared Data в dll.
C>Так вот как определить, кто-то уже залогинен или нет?
из Winlogon C> И если да, то user token получить?
токен передаетя, новытащить ето я не смог, все изыскания ниже, токены нуждаются в доработке(т.е. не работают). Количество пользователей определяется точно. Я жду когда их число будет не 0 и тогда запускаю программу под System из сервиса(кстати, работать с WTS желательно из сервиса, у него прав побольше). Возможно процесс, для которого копируются токены (токен также нужно сделать праймари для использования CreateProcessAsUser!) должен активировать у себя некую привелегию.
Сможеш — напишы!
C> С Terminal Services хоть как-то понятно, а обычная Win2K? Всякие WTS* хоть на ней и определены, но не работают.
Они просто определены но не реализованы.
Также помни что одна копия dll будет подгружена в Winlogon, другая в твое приложения. Передача данных через раздел глобальных данных в dll
Код в dll
Шаред дата глобально в пределах сессии, что подходит для 2к - там она в системе только одна
#pragma data_seg(".SHARDATA")
//Признак активности пользователяstatic bool isUserActive = true;
//Факт наступления активности пользователя обработанstatic bool isUserActiveApply = false;
static bool IsStartScreenSaver = false;
static int ActiveUserLoginCount = 0;
// токен последнего залогинившегося пользователяstatic HANDLE hTokenLastLoginUser = 0;
//ПИД процесса, определившего токенstatic int PIDDetermineTokenProcess = -1;
static bool IsActiveUserLock = false;
static bool IsTest = false; //признак отладки
// Global variable
CRITICAL_SECTION CriticalSectionTimerProc;
//ВНИМАНИЯ Можно получить корректное значение
//только подгрузив длл из приложения в 0 терминальной сессииstatic char CurUserName[1024] = ""; // имя(логин) активного интерактивного пользователя#pragma data_seg()
//---------------------------------------------------------------------------
// Windows Notification Package
//
// Winlogon Notification Events
// Here is the event handlers for the Winlogon Notification Events
// Дополнительная информация:
// Данные функции будут автоматически вызыватся системой (logon.exe)
// при наступлении соотв. событий в том случае, если данная dll и
// соответствие функций событиям будут зарегистрированы в реестре
// см. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/wlx_notification_info.asp
//Lock This event occurs when the user locks the workstation. __declspec( dllexport ) void WLEventLock(PWLX_NOTIFICATION_INFO pInfo)
{
// запоминаем пользователя
WideCharToMultiByte(CP_ACP, 0, pInfo->UserName,-1,CurUserName, 1024, NULL, NULL );
IsActiveUserLock = true;
//
//WriteToRegistreeLastActiveUser("Lock",pInfo->UserName);
//SendWLENotifyMessage(WM_SendWLEventLock);
}
//Unlock This event occurs when the user unlocks the workstation or when a system administrator overrides the lock and logs the user off. __declspec( dllexport ) void WLEventUnlock(PWLX_NOTIFICATION_INFO pInfo)
{
// запоминаем пользователя
WideCharToMultiByte(CP_ACP, 0, pInfo->UserName,-1,CurUserName, 1024, NULL, NULL );
IsActiveUserLock = false;
//
//WriteToRegistreeLastActiveUser("Unlock",pInfo->UserName);
//SendWLENotifyMessage(WM_SendWLEventUnlock);
//MessageBox(NULL,"UNLOCK","WNP",0);
// Пользователь системы == последний активный интерактивный(каламбур) пользователь
//WriteToRegistreeLastActiveUser("LA",pInfo->UserName);
//SendWLENotifyMessage(WM_SendChangeUser);
}
//Logon This event occurs when a user logs on the system.
//Note that the Logon event occurs before the user's network connections are restored. If your event handler requires access to the user's network connections, it should handle the StartShell event instead of the Logon event.__declspec( dllexport ) void WLEventLogon(PWLX_NOTIFICATION_INFO pInfo)
{
// запоминаем пользователя
WideCharToMultiByte(CP_ACP, 0, pInfo->UserName,-1,CurUserName, 1024, NULL, NULL );
ActiveUserLoginCount++;
// Запуск потока внутри winlogon для контроля системы монитора активности
StartProtectThread();
/*SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = true;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);*/
hTokenLastLoginUser = pInfo->hToken;
if (DuplicateTokenEx(pInfo->hToken,MAXIMUM_ALLOWED ,NULL, SecurityDelegation,TokenPrimary,&hTokenLastLoginUser))
{
PutsInMyFile("Токен успешно продублирован \n");
/*STARTUPINFO si;
PROCESS_INFORMATION pi;
// Initialize the STARTUPINFO structure.
// Specify that the process runs in the interactive desktop.
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb= sizeof(STARTUPINFO);
si.lpDesktop = TEXT("winsta0\\default");
bool bRes = CreateProcessAsUser(
hTokenLastLoginUser,
NULL,
"calc.exe",
NULL,
NULL,
false,
NORMAL_PRIORITY_CLASS | CREATE_DEFAULT_ERROR_MODE,
NULL,
NULL,
&si,
&pi
);*/
}
else
{
PutsInMyFile("Токен не продублирован \n");
}
char str[1024];
sprintf(str, "Пользователь %d залоинился\n", (int)pInfo->hToken);
PutsInMyFile(str);
PIDDetermineTokenProcess = GetCurrentProcessId();
}
//Logoff This event occurs when a user logs off from the system. The Logoff event is performed synchronously, even if the notification package's registry settings indicate that it can handle events asynchronously. __declspec( dllexport ) void WLEventLogoff(PWLX_NOTIFICATION_INFO pInfo)
{
// запоминаем пользователя
WideCharToMultiByte(CP_ACP, 0, pInfo->UserName,-1, CurUserName, 1024, NULL, NULL );
ActiveUserLoginCount--;
hTokenLastLoginUser = (HANDLE) NULL;
PIDDetermineTokenProcess = -1;
//WriteToRegistreeLastActiveUser("Logoff",pInfo->UserName);
//SendWLENotifyMessage(WM_SendWLEventLogoff);
}
//Shutdown This event occurs just before the system shuts down. __declspec( dllexport ) void WLEventShutdown(PWLX_NOTIFICATION_INFO pInfo)
{
//WriteToRegistreeLastActiveUser("Shutdown",pInfo->UserName);
//SendWLENotifyMessage(WM_SendWLEventShutdown);
}
//StartScreenSaver This event occurs when the screen saver has started. Typically, this happens after a user has been inactive for a set period of time.
//Functions handling this event should not display a user interface. StartScreenSaver event notification is intended for informational purposes only.__declspec( dllexport ) void WLEventStartScreenSaver(PWLX_NOTIFICATION_INFO pInfo)
{
//WriteToRegistreeLastActiveUser("StartScreenSaver",pInfo->UserName);
//SendWLENotifyMessage(WM_SendWLEventStartScreenSaver);
}
//StartShell This event occurs after the user has logged onto the system, network connections have been established, and the user specified shell program, (usually Explorer.exe), has been started. __declspec( dllexport ) void WLEventStartShell(PWLX_NOTIFICATION_INFO pInfo)
{
/*SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = true;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
if (DuplicateTokenEx(pInfo->hToken,MAXIMUM_ALLOWED ,&sa, SecurityDelegation,TokenPrimary,&hTokenLastLoginUser))
{
PutsInMyFile("Токен успешно продублирован \n");
STARTUPINFO si;
PROCESS_INFORMATION pi;
// Initialize the STARTUPINFO structure.
// Specify that the process runs in the interactive desktop.
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb= sizeof(STARTUPINFO);
si.lpDesktop = TEXT("winsta0\\default");
bool bRes = CreateProcessAsUser(
hTokenLastLoginUser,
NULL,
"pbrush.exe",
NULL,
NULL,
false,
NORMAL_PRIORITY_CLASS | CREATE_DEFAULT_ERROR_MODE,
NULL,
NULL,
&si,
&pi
);
}
else
{
PutsInMyFile("Токен не продублирован \n");
} */
// запоминаем пользователя
/*WideCharToMultiByte(CP_ACP, 0, pInfo->UserName,-1, szBuf, 1024, NULL, NULL );
WriteToRegistreeLastActiveUser("StartShell",pInfo->UserName);
SendWLENotifyMessage(WM_SendWLEventStartShell);
WriteToRegistreeLastActiveUser("LA",pInfo->UserName);
SendWLENotifyMessage(WM_SendChangeUser);*/
}
//Startup This event occurs when the system is started up or rebooted. __declspec( dllexport ) void WLEventStartup(PWLX_NOTIFICATION_INFO pInfo)
{
//WriteToRegistreeLastActiveUser("Startup",pInfo->UserName);
}
//StopScreenSaver This event occurs when the screen saver has stopped. Typically this happens when there is keyboard or mouse activity.
//Functions handling this event should not display a user interface. StopScreenSaver event notification is intended for informational purposes only. __declspec( dllexport ) void WLEventStopScreenSaver(PWLX_NOTIFICATION_INFO pInfo)
{
//WriteToRegistreeLastActiveUser("StopScreenSaver",pInfo->UserName);
//SendWLENotifyMessage(WM_SendWLEventStopScreenSaver);
}
//РАБОТАЕТ!__declspec(dllexport) char* GetCurUserName()
{return CurUserName;};
/*__declspec(dllexport) bool GetCurTokenInfo(PHANDLE phToken, int* PID)
{
*phToken = hTokenLastLoginUser;
*PID = PIDDetermineTokenProcess;
if ((*phToken>=0)&&(PID>=0))
{
PutsInMyFile("Передал токен пользователя\n");
return true;
}
else
{
PutsInMyFile("Не смог передать токен пользователя\n");
return false;
}
};
*/__declspec(dllexport) bool GetCurTokenInfo(PHANDLE phToken, int)
{
char e[1056];
sprintf(e,"%s token: %d PIDDetermineTokenProcess:%d\n","<DLL> Вход в получение токена", hTokenLastLoginUser,PIDDetermineTokenProcess);
PutsInMyFile(e);
if (hTokenLastLoginUser<=0)
return false;
if (PIDDetermineTokenProcess<0)
return false;
HANDLE hSource = GetProcessHandleWithEnoughRights(
PIDDetermineTokenProcess,
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ|PROCESS_DUP_HANDLE);
if (hSource ==NULL)
PutsInMyFile("Не могу открыть Winlogon\n");
if (hSource != NULL)
{
char e[1056];
bool r = false;
HANDLE hp = GetCurrentProcess();
if (DuplicateHandle(
hSource,
hTokenLastLoginUser,
hp,
phToken,
DUPLICATE_SAME_ACCESS,
false,
0) !=0)
{
sprintf(e,"%s %d ","<DLL> Токен скопирован\n", GetLastError());
PutsInMyFile(e);
r = true;
}
else
{
sprintf(e,"%s %d ","<DLL> Токен не скопирован\n", GetLastError());
PutsInMyFile(e);
}
CloseHandle(hp);
CloseHandle(hSource);
return r;
}
sprintf(e,"%s %d ","<DLL> Ошибка копирования токена", GetLastError());
PutsInMyFile(e);
return false;
};
__declspec(dllexport) bool GetIsStartScreenSaver()
{return IsStartScreenSaver;};
__declspec(dllexport) int GetActiveUserLoginCount()
{return ActiveUserLoginCount;};
__declspec(dllexport) bool GetIsActiveUserLock()
{return IsActiveUserLock;};
Здравствуйте, acronim, Вы писали:
C>>Так вот как определить, кто-то уже залогинен или нет? A>из Winlogon
В упор не нашел.
C>> И если да, то user token получить? A>токен передаетя, новытащить ето я не смог, все изыскания ниже, токены нуждаются в доработке(т.е. не работают). Количество пользователей определяется точно. Я жду когда их число будет не 0 и тогда запускаю программу под System из сервиса(кстати, работать с WTS желательно из сервиса, у него прав побольше). Возможно процесс, для которого копируются токены (токен также нужно сделать праймари для использования CreateProcessAsUser!) должен активировать у себя некую привелегию. A>Сможешь — напиши!
Не буду даже пробовать, похоже. Как я понял, для инсталляции/удаления этих notification packages требуется перезагрузка системы, меня это как-то не устраивает.
А токен сделать primary как-то можно через DuplicateHandleEx, енсли верить MSDN.
C>Не буду даже пробовать, похоже. Как я понял, для инсталляции/удаления этих notification packages требуется перезагрузка системы, меня это как-то не устраивает.
Для инталяции — да, для деинсталяции — нет.
Если инсталяцию провести до логина ползователя, то возможно перегружать не надо — требует проверки.
А почему не устраивает? C>А токен сделать primary как-то можно через DuplicateHandleEx, енсли верить MSDN.
Здравствуйте, acronim, Вы писали:
C>> Не буду даже пробовать, похоже. Как я понял, для инсталляции/удаления этих notification packages требуется перезагрузка системы, меня это как-то не устраивает. A> Для инталяции — да, для деинсталяции — нет. A> Если инсталяцию провести до логина ползователя, то возможно перегружать не надо — требует проверки.
У меня сложилось впечатление, что winlogon как при запуске загружает перечисленный DLL-ки, так и держит.
A> А почему не устраивает?
Хотелось бы эту операцию максимально оперативно и безболезненно делать.
И, опять же, nnCron как-то явно это делает без таких сложностей, но как — не врубаюсь
Здравствуйте, Cavaler, Вы писали:
C>Здравствуйте, acronim, Вы писали:
C>>> Не буду даже пробовать, похоже. Как я понял, для инсталляции/удаления этих notification packages требуется перезагрузка системы, меня это как-то не устраивает. A>> Для инталяции — да, для деинсталяции — нет. A>> Если инсталяцию провести до логина ползователя, то возможно перегружать не надо — требует проверки. C>У меня сложилось впечатление, что winlogon как при запуске загружает перечисленный DLL-ки, так и держит.
A>> А почему не устраивает?
C>Хотелось бы эту операцию максимально оперативно и безболезненно делать.
Можно на 2к получить пароль пользователя из сервиса. Далее зная имя и пароль получить токен пользователя дело техники, вот только для пароля нужно недокументированными функциями пользоватся, что не есть хорошо.
C>И, опять же, nnCron как-то явно это делает без таких сложностей, но как — не врубаюсь
Что такое nnCron? Можно поподробней?