EternalWindows
セキュリティコンテキスト / ユーザープロファイル

ユーザープロファイルには、特定のユーザーに関する情報が含まれています。 たとえば、コントロールパネルで設定できる情報や環境変数、 ユーザー別にインストールされたアプリケーション情報などがあります。 システムにコンソールを通じてログオンした場合は、 ユーザープロファイルがWinlogon(正確にはUserinit)によってロードされますが、 LogonUserを呼び出しただけでは、そのユーザーのユーザープロファイルはロードされません。 つまり、ログオンしたユーザーが上記した情報にアクセスするためには、 明示的にユーザープロファイルをロードする必要があります。

ユーザープロファイルの正体は、ntuser.datです。 これをレジストリにロードすることにより、HKEY_USERS以下にサブキーが構築され、 ntuser.datに格納されている情報にアクセスできるようになります。 HKEY_USERS以下には、次のようなサブキーが存在します。

サブキー サブキーを参照するユーザー
.DEFAULT プロファイルがロードされていないユーザーが参照する。
S-1-5-18 SYSTEMが参照する。
S-1-5-19 LOCAL SERVICEが参照する。
S-1-5-20 NETWORK SERVICEが参照する
S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXX 特定のユーザーが参照する。SIDの最後の4文字はRIDであり、各ユーザーはこれで識別される。 RIDを除いた残りのSIDは、コンピュータのSIDである。

HKEY_USERSには、システムに存在するユーザーが列挙されているわけではありません。 あくまで、ユーザープロファイルがロードされたユーザーが列挙されているだけです。 ユーザープロファイルを持っていても、それがロードされていなければ、 列挙されないことに注意してください。 ユーザープロファイルを持っているユーザーは、次のレジストリキー以下に列挙されています。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\ProfileList

このレジストキー以下には、HKEY_USERSと同じようにユーザーのテキスト形式のSIDが列挙されています。 各サブキーには、ユーザープロファイルが保存されているフォルダのパスなどが格納されています。

ユーザープロファイル、つまりntuser.datをレジストリにロードするためには、 それがどこに格納されているかを特定しなければなりません。 これは先に示したレジストリキーからも確認できますが、 userenv(UserEnvironment)の関数群を呼び出した方が効率的です。 次に、プロファイルディレクトリ(ntuser.datを格納するディレクトリ)を取得する いくつかの関数の使い方を示します。

#include <windows.h>
#include <userenv.h>

#pragma comment (lib, "userenv.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE hToken;
	DWORD  dwSize;
	TCHAR  szDirectory[256];

	dwSize = sizeof(szDirectory) / sizeof(TCHAR);
	GetProfilesDirectory(szDirectory, &dwSize);
	MessageBox(NULL, szDirectory, TEXT("GetProfilesDirectory"), MB_OK);

	dwSize = sizeof(szDirectory) / sizeof(TCHAR);
	GetDefaultUserProfileDirectory(szDirectory, &dwSize);
	MessageBox(NULL, szDirectory, TEXT("GetDefaultUserProfileDirectory"), MB_OK);

	OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
	dwSize = sizeof(szDirectory) / sizeof(TCHAR);
	GetUserProfileDirectory(hToken, szDirectory, &dwSize);
	MessageBox(NULL, szDirectory, TEXT("GetUserProfileDirectory"), MB_OK);
	CloseHandle(hToken);

	return 0;
}

ユーザープロファイルを扱う関数を呼び出すには、 userenv.hのインクルードとuserenv.libのリンクが必要となります。 上記コードで呼び出している3つの関数が、それぞれどのような文字列を返すかを示します。

関数 パス
GetProfilesDirectory C:\Users
GetDefaultUserProfileDirectory C:\Users\Default
GetUserProfileDirectory C:\Users\[username]

GetUserProfileDirectoryが便利な関数です。 この関数の第2引数にトークンを指定すれば、トークンが表すユーザーの プロファイルディレクトリを取得することができます。 つまり、LogonUserで取得したトークンを指定すれば、 ログオンしたユーザーのプロファイルディレクトリを取得できることになります。 なお、Usersという文字列は、Windows XPではDocuments and Settingsとなっています。

特定のユーザーのプロファイルディレクトリを取得すれば、 それとntuser.datを連結することでパスが完成します。 後はこれをRegLoadKeyという関数に指定すれば、ユーザープロファイルがロードされることになりますが、 実際にはこのような複雑な手順を行う必要はありません。 次に示すLoadUserProfileを呼び出せば、 トークンで表されるユーザーのユーザープロファイルがロードされるからです。

BOOL WINAPI LoadUserProfile(
  HANDLE hToken,
  LPPROFILEINFO lpProfileInfo
);

hTokenは、トークンのハンドルを指定します。 lpProfileInfoは、PROFILEINFO構造体のアドレスを指定します。 PROFILEINFO構造体の定義は次のようになっています。

typedef struct _PROFILEINFO {
  DWORD dwSize;
  DWORD dwFlags;
  LPTSTR lpUserName;
  LPTSTR lpProfilePath;
  LPTSTR lpDefaultPath;
  LPTSTR lpServerName;
  LPTSTR lpPolicyPath;
  HANDLE hProfile;
} PROFILEINFO, *LPPROFILEINFO;

dwSizeは、構造体のサイズを指定します。 dwFlagsは、0で問題ありません。 lpUserNameは、ユーザー名を指定します。 lpProfilePathは、NULLまたは移動ユーザープロファイルのパスを指定します。 lpProfilePathは、NULLまたはデフォルトのプロファイルのパスを指定します。 lpServerNameは、NULLまたはドメインコントローラ名を指定します。 lpPolicyPathは、NULLまたはポリシーファイルのパスを指定します。 hProfileは、ユーザープロファイルをロードしたレジストリキーを表すハンドルが格納されます。

ロードしたユーザープロファイルは、ユーザーのログオフなどで自動的にアンロードされることはありません。 アンロードを行いたい場合は、UnloadUserProfileを呼び出します。

BOOL WINAPI UnloadUserProfile(
  HANDLE hToken,
  HANDLE hProfile
);

hTokenは、トークンのハンドルを指定します。 hProfileは、アンロードしたいレジストリキーのハンドルを指定します。 LoadUserProfileで初期化したPROFILEINFO.hProfileを指定することもできます。

ユーザープロファイルをロードすれば、環境変数などの情報を取得することができます。 次に示すExpandEnvironmentStringsForUserは、指定したユーザーとして環境変数を展開します。

BOOL WINAPI ExpandEnvironmentStringsForUser(
  HANDLE hToken,
  LPCTSTR lpSrc,
  LPTSTR lpDest,
  DWORD dwSize
);

hTokenは、トークンのハンドルを指定します。 lpSrcは、環境変数を表す文字列を指定します。 lpDestは、環境変数を展開した文字列を受け取るバッファを指定します。 dwSizeは、lpDestに指定したバッファのサイズを指定します。

今回のプログラムは指定したユーザーをログオンさせ、 そのユーザーのユーザープロファイルをロードします。 そして、ログオンしたユーザーとして環境変数を展開しています。

#include <windows.h>
#include <userenv.h>

#pragma comment (lib, "userenv.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR       szUserName[] = TEXT("Guest");
	TCHAR       szPassword[] = TEXT("");
	TCHAR       szEnv[] = TEXT("%USERNAME%");
	TCHAR       szValue[256];
	HANDLE      hToken;
	PROFILEINFO profileInfo;

	if (!LogonUser(szUserName, NULL, szPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) {
		MessageBox(NULL, TEXT("ログオンに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	ZeroMemory(&profileInfo, sizeof(PROFILEINFO));
	profileInfo.dwSize     = sizeof(PROFILEINFO);
	profileInfo.lpUserName = szUserName;

	if (!LoadUserProfile(hToken, &profileInfo)) {
		if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD)
			MessageBox(NULL, TEXT("必要な特権が割り当てられていません。"), NULL, MB_ICONWARNING);
		else
			MessageBox(NULL, TEXT("ユーザープロファイルのロードに失敗しました。"), NULL, MB_ICONWARNING);
		CloseHandle(hToken);
		return 0;
	}
	
	ExpandEnvironmentStringsForUser(hToken, szEnv, szValue, sizeof(szValue));
	MessageBox(NULL, szValue, szEnv, MB_OK);

	UnloadUserProfile(hToken, profileInfo.hProfile);
	CloseHandle(hToken);

	return 0;
}

LoadUserProfileを呼び出すには、PROFILEINFO構造体のdwSizeとlpUserNameを明示的に初期化しておきます。 この関数を呼び出すには、呼び出し側プロセスのトークンにSE_RESTORE_NAMEとSE_BACKUP_NAMEという2つの特権が 割り当てられている必要があるため、制限ユーザーは関数の呼び出しに失敗することになります。 ExpandEnvironmentStringsForUserでは、%USERNAME%という環境変数を展開しています。 これにより、hTokenで表されるユーザーのユーザー名を取得することができます。 しかし、ユーザープロファイルがロードされていない状態でこの関数を呼び出すと、 正しい値は取得できません。


戻る