Как правильно олицетворять пользователя из службы?

Я работаю со службой, которая должна олицетворять вошедшего в систему пользователя.

Мой код до сих пор с базовой обработкой ошибок:

 // get the active console session ID of the logged on user
if ( !WTSQueryUserToken( WTSGetActiveConsoleSessionId(), &hToken ) )
{
    ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
    return;
}

HANDLE hDuplicated;

// duplicate the token
if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
{
    ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
}
else 
{
    ShowErrorText( "DuplicateToken succeeded.", 0, true );
}

// impersonate the logged on user
if ( !ImpersonateLoggedOnUser( hToken ) )
{
    ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError(), true );
    return;
}

// retrieve the DC name 
if ( !GetPrimaryDC( DC ) )
{
    ShowErrorText( "GetPrimaryDC failed.", 0, true );
}
PROFILEINFO lpProfileInfo;

ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
lpProfileInfo.dwSize = sizeof( PROFILEINFO );
lpProfileInfo.lpUserName = CurrentUser;

// get type of profile. roaming, mandatory or temporary
int ret = GetTypeOfProfile();
if ( ret == 2 )
{
    // if roaming profile get the path of it
    if ( !GetRoamingProfilePath( DC, CurrentUser, RoamingProfilePath ) )
    {
        ShowErrorText( "Failed to retrieve roaming profile path.", GetLastError(), true );
    }
}
if ( RevertToSelf( ) )
{
    ShowErrorText( "Impersonation ended successfully.", 0, true );
}

 if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
{
    ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
}
else
{
    ShowErrorText( "LoadUserProfile succeeded.", 0, true );
}

   //do some stuff


  if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
{
    ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
}
else
{
    ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
}

 if ( !ImpersonateLoggedOnUser( hToken ) )
{
    ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError( ), true );
    return;
}

Согласно MSDN:

Когда пользователь входит в систему в интерактивном режиме, система автоматически загружает профиль пользователя. Если служба или приложение выдает себя за пользователя, система не загружает профиль пользователя. Поэтому служба или приложение должны загрузить профиль пользователя с помощью LoadUserProfile.

Службы и приложения, вызывающие LoadUserProfile, должны проверять, есть ли у пользователя перемещаемый профиль. Если у пользователя есть перемещаемый профиль, укажите его путь как элемент lpProfilePath PROFILEINFO. Чтобы получить путь к перемещаемому профилю пользователя, вы можете вызвать функцию NetUserGetInfo, указав уровень информации 3 или 4.

После успешного возврата элемент hProfile в PROFILEINFO представляет собой дескриптор ключа реестра, открытый в корень куста пользователя. Он был открыт с полным доступом (KEY_ALL_ACCESS). Если службе, которая выдает себя за пользователя, необходимо прочитать или записать файл реестра пользователя, используйте этот дескриптор вместо HKEY_CURRENT_USER. Не закрывайте дескриптор hProfile. Вместо этого передайте его функции UnloadUserProfile.

Если я использую свой код, как сейчас, то он работает. Однако это немного странно, потому что сначала я должен олицетворять вошедшего в систему пользователя, а затем завершать олицетворение, чтобы загрузить профиль пользователя. Если я не завершу олицетворение, то LoadUserProfile завершится с ошибкой 5 (отказано в доступе). И после того, как LoadUserProfile преуспел, я должен снова выдать себя за пользователя?

Так вот у меня вопрос, так и должно быть, или я что-то не так делаю? Другой вопрос заключается в том, что если LoadUserProfile преуспеет, я смогу использовать hProfile в качестве дескриптора для реестра зарегистрированных пользователей. Вопрос как? Потому что для использования RegOpenKeyEy и RegSetValueEx мне нужно передать HKEY, а не HANDLE. Итак, как я могу использовать эту ручку?

Благодарить!


person kampi    schedule 05.11.2013    source источник


Ответы (1)


Вам не нужно вызывать ImpersonateLoggedOnUser(), так как вы передаете токен пользователя LoadUserProfile(). Вызывайте ImpersonateLoggedOnUser() только в том случае, если вам нужно вызывать API, которые не позволяют передавать им токен пользователя.

Если вы прочитаете остальную часть документации LoadUserProfile(), там сказано:

Вызывающий процесс должен иметь привилегии SE_RESTORE_NAME и SE_BACKUP_NAME.

Выдавая себя за пользователя, для которого вы пытаетесь загрузить профиль, вы, вероятно, теряете эти привилегии. Так что не выдавайте себя за пользователя.

Обновление: попробуйте следующее:

// get the active console session ID of the logged on user
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
if ( dwSessionID == 0xFFFFFFFF )
{
    ShowErrorText( "WTSGetActiveConsoleSessionId failed.", GetLastError( ), true );
    return;
}

if ( !WTSQueryUserToken( dwSessionID, &hToken ) )
{
    ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
    return;
}

// duplicate the token
HANDLE hDuplicated = NULL;
if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
{
    ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
    CloseHandle( hToken );
    return;
}

// retrieve the DC name 
if ( !GetPrimaryDC( DC ) )
{
    ShowErrorText( "GetPrimaryDC failed.", 0, true );
    CloseHandle( hDuplicated );
    CloseHandle( hToken );
    return;
}

PROFILEINFO lpProfileInfo;
ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
lpProfileInfo.dwSize = sizeof( PROFILEINFO );
lpProfileInfo.lpUserName = CurrentUser;

// get type of profile. roaming, mandatory or temporary
USER_INFO_4 *UserInfo = NULL;
int ret = GetTypeOfProfile();
if ( ret == 2 )
{
    // if roaming profile get the path of it
    if ( NetUserGetInfo( DC, CurrentUser, 4, (LPBYTE*)&UserInfo) != NERR_Success )
    {
        ShowErrorText( "NetUserGetInfo failed.", 0, true );
        CloseHandle( hDuplicated );
        CloseHandle( hToken );
        return;
    }

    lpProfileInfo.lpProfilePath = UserInfo->usri3_profile;
}

if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
{
    ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
    if ( UserInfo )
        NetApiBufferFree(UserInfo);
    CloseHandle( hDuplicated );
    CloseHandle( hToken );
    return;
}

if ( UserInfo )
    NetApiBufferFree(UserInfo);

ShowErrorText( "LoadUserProfile succeeded.", 0, true );

//do some stuff

if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
{
    ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
}
else
{
    ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
}

CloseHandle( hDuplicated );
CloseHandle( hToken );

Что касается реестра, дескриптор hProfile является открытым HKEY для дерева HKEY_CURRENT_USER пользователя. Просто преобразуйте его с HANDLE в HKEY при передаче в функции Registry API. Он уже открыт, поэтому вам не нужно вызывать RegOpenKeyEx(), чтобы снова открыть тот же ключ, но вы можете использовать его в качестве корневого ключа при создании/открытии подразделов или чтении/записи значений в корневом ключе.

person Remy Lebeau    schedule 05.11.2013
comment
Я немного запутался. Я вызвал ImpersonateLoggedonUser в первый раз, потому что мне нужно узнать, есть ли у пользователя перемещаемый профиль. Если да, то мне нужен его путь и, конечно же, имя пользователя. Мне нужно, чтобы эти штуки вызывали LoadUserProfile. Если я не использую ImpersonateLoggedOnUser, то как я могу их получить? - person kampi; 05.11.2013
comment
Прочтите LoadUserProfile() документацию: получение информации о перемещении пользователя путь к профилю, вы можете вызвать функцию NetUserGetInfo(), указание уровня информации 3 или 4. Для этого вам не нужно олицетворение. Также прочитайте комментарии о перемещаемых профилях в нижней части PROFILEINFO документации. . - person Remy Lebeau; 06.11.2013
comment
Спасибо большое за помощь! Это работает, хотя у меня есть последняя проблема. Untul теперь я использовал GetUsername, чтобы получить имя вошедшего в систему пользователя. Поскольку я использовал ImpersonateLoggedOnUser, он вернул мне правильное имя пользователя, которое я должен передать в PROFILINFO. Но как мне получить его сейчас? - person kampi; 06.11.2013
comment
GetUserName() возвращает имя пользователя, связанное с вызывающим потоком. Вот почему вы должны использовать олицетворение при вызове GetUserName() из службы. Поскольку у вас уже есть идентификатор сеанса, из которого исходит токен пользователя, вы можете использовать WTSQuerySessionInformation() для запроса имени пользователя, вошедшего в сеанс. Установите для параметра WTSInfoClass значение WTSUserName. Вы также можете использовать WTSDomainName для запроса домена, в котором зарегистрирован сеанс. - person Remy Lebeau; 06.11.2013
comment
Хорошо, спасибо. Это работает. Однако обнаруживается другая Проблема. Надеюсь последний. Я использую GetProfileType(), чтобы получить тип профиля. Который до сих пор работал нормально, потому что я использовал олицетворение. К сожалению, сейчас выдает, что у пользователя локальный профиль, а не перемещаемый. Это также связано с тем, что он получит тип профиля вызывающего потока. Знаете ли вы какое-то решение, как я могу получить тип профиля пользователя, вошедшего в систему? Заранее спасибо! - person kampi; 06.11.2013
comment
Я бы проигнорировал GetProfileType() и просто безоговорочно вызвал NetGetUserInfo(). Тем более что в GetProfileType() документации говорится: Если пользователь профиль еще не загружен, функция не работает, и вы еще не загрузили профиль пользователя. Информация о пользователе либо будет иметь перемещаемый профиль, либо нет. - person Remy Lebeau; 06.11.2013
comment
Спасибо большое за помощь. С вашей помощью я смог исправить свой код. - person kampi; 07.11.2013
comment
@RemyLebeau: Эй, я знаю, что ты давно спрашивал об этом. У меня аналогичная проблема, и мне интересно, как вы определяете тип профиля в функции GetTypeOfProfile? А именно перемещаемый профиль пользователя. Спасибо. - person c00000fd; 12.11.2016
comment
@ c00000fd: это должно было быть опубликовано как новый вопрос. Но если вы внимательно перечитаете мой предыдущий комментарий, я сказал: Я бы проигнорировал GetProfileType() и просто безоговорочно позвонил бы NetGetUserInfo()... Информация о пользователе либо будет иметь перемещаемый профиль, либо нет. Прочтите NetGetUserInfo документацию. - person Remy Lebeau; 12.11.2016