Windows NTでユーザーアカウントを操作する方法

戻る

WindowsNTでは,システムリソースへのアクセスはユーザアカウントの権限に したがった範囲で実行されます.
サービスから処理を実行するアプリケーションを作成し,SYSTEMの権限でなく処理を 要求したユーザの権限でで実行したい場合,インストーラで処理に利用するユーザを あらかじめ作成しておき,Win32 API LogonUser などを利用する必要があります. IIS(Internet Information Server)やSQLサーバなどのように,アプリケーションで 特定のユーザアカウントを必要とするものは,インストーラでユーザカウントを 自動生成しています.
また,ユーザマネージャでは,バッチ処理でユーザを設定する機能がありません. ユーザマネージャで複数のユーザを同時に設定するのは大変ですが, ユーザ設定コマンドを作成することにより,複数のユーザをバッチ処理で設定すること が可能になります.
ということで,プログラムからユーザアカウントを作成する方法について考えて 見ます.

14-1 ユーザアカウントを制御するには

ユーザアカウントを制御する,セキュリティアカウント API は,LAN Manager 2.0 を利用したLANを強く意識した形態になっています.このため,一見システムの アカウントとは無関係のように見えます.しかし,これらのAPIを利用することにより, WindowsNTのユーザマネージャ(USRMGR.EXE)で設定できるほとんどの項目を操作すること ができ,セキュリティデータベース内のユーザーのアカウントを制御することが できます.ユーザーまたはアプリケーションは,セキュリティデータベース内に アカウントを持っていなければシステムリソースにアクセスすることが できません.
WindowsNTの SAM(Security Account Manager) は,このユーザーアカウントを使って, ユーザーやアプリケーションがリソースの使用アクセス権を持っていることを確認 します.
セキュリティアカウント API の関数名は,"Net" で始まりますが,"Net"で始まる 名前の持つAPIには,ユーザ管理以外にリソースの共有や分散ファイルシステム関連の 機能も含まれます.
ここでは,ユーザアカウントを制御する機能を中心に説明しますが,他の機能も ほとんどが同じように扱うことができますので,参考になると思います.

セキュリティアカウントAPI

セキュリティアカウントAPIには,ユーザアカウントを操作するAPIとグループを操作 するAPIがあります.ユーザごとにアクセス権を設定することもできますが,ユーザは グループに属することにより,各グループに設定されたアクセス権を引き継ぐことが できます.

●アカウントを操作するAPI
アカウントの生成・削除を行うには,Win32 API NetUserAdd, Win32 API NetUserDel を利用します.これらの関数はローカルアカウント以外に,ドメインのユーザを追加 することもできます.これらの関数を利用するには,システム管理者の権限が必要に なります.また,NetUserAdd でコンピュータアカウントを追加するアプリケーション (プロセス)は,"SeMachineAccountPrivilege"特権が必要になります.
コンピュータ名は "MACHINE$" のように末尾に"$"を設定しておく必要があります.  ユーザを追加するには,USER_INFO_n(n=0〜3) 構造体に各情報を設定して NetUserAdd を呼び出します.一度追加したアカウントの情報を取得・設定するには Win32 API NetUserGetInfo, Win32 API NetUserSetInfo を利用します.
 アカウントの情報を取得するための特別な権限は必要ありませんが,設定するには, システム管理者またはアカウントオペレータの権限が,自分自身のアカウントで ログオンしておく必要があります.
現在設定されているアカウントの一覧を取得するには,Win32 API NetUserEnum を 利用します.
Net API は,UNICODE 版しか存在しませんので,ANSI 版のアプリケーションで これらの関数を利用する場合は,文字列をUNICODEに変換しなければなりません.

●NetUser API の使い方
新しいアカウントを作成する場合,どのような用途のアカウントであるかを示す 必要があります.アカウントのタイプは,USER_INFO_1 構造体の usri1_flags メンバ に設定された値が利用されます.通常のアカウントを作成するには,
usrinfo1.usri1_flags = UF_NORMAL_ACCOUNT | UF_SCRIPT;
を設定します.このフラグを設定して作成されたアカウントは,ユーザマネージャで 作成されたローカルマシンのアカウントと同等のものになります.
その他のアカウントでは,ユーザマネージャでは表示されないため,正しく作成された かを確認するには,NET.EXEを利用します.
設定されているアカウントは
>NET USER
を実行することで表示されます.また,
>NET USER アカウント名
で,アカウントの詳細な情報が表示されます.
レベルの低いUSER_INFO_n構造体でユーザを作成した場合には, 指定していない項目に既定値が設定されます.

●グループを操作するAPI
グループには,グローバルグループとローカルグループがあります. 通常,WindowsNTで利用する組み込みグループ(Administrators, Power Users, Users, Guests グループ)は,ローカルグループです.
グローバルグループは,ドメインコントローラが動作している環境で利用するグループ で,ドメインに参加しているマシン間で共通に利用することができます. このため,ドメインコントローラが存在しない環境では,グローバルグループを利用 できません.
Win32 API NetLocalGroupSetMembers ではユーザを指定するために, Win32 API LookupAccountName を利用して,ユーザアカウントのSID(セキュリティ 識別子)を取得する必要があります.また,新しくアカウントを追加するときには, すでに設定されているアカウントを Win32 API NetLocalGroupGetMembers で取得し, 新しく設定するアカウントを追加してから,再設定する必要があります.

表14-1:ユーザアカウント操作関数
NET_API_STATUS NetUserAdd(LPWSTR lpwszServerName,
 DWORD dwLevel, LPBYTE lpbBuff, LPDWORD dwParmErr)
ユーザーアカウントを追加する

引数
LPWSTR lpwszServerName ... リモートサーバーの名前
 NULLポインタやNULL文字列はローカルマシン
DWORD dwLevel ... USER_INFO_n構造体のレベル(0〜3)
LPBYTE lpbBuff ... USER_INFO_n構造体を指すポインタ
LPDWORD lpdwParamErr ... エラー時にERROR_INVALID_PARAMETERが返されたときに第1パラメータのインデックスを返す不要なときはNULLを指定する

戻り値
正常終了 NERR_Success
異常終了 ERROR_ACCESS_DENIED
 ERROR_INVALID_PARAMETER
 NERR_InvalidComputer
 NERR_NotPrimary
 NERR_GroupExists
 NERR_UserExists
 NERR_PasswordTooShort
NET_API_STATUS NetUserDel(
 LPWSTR lpwszServerName, LPWSTR lpwszUserName)
ユーザーアカウントを削除する

引数
LPWSTR lpwszServerName ... リモートサーバーの名前
 NULLポインタやNULL文字列はローカルマシン
LPWSTR lpwszUserName ... 削除するアカウント名

戻り値
正常終了 NERR_Success
異常終了 ERROR_ACCESS_DENIED
 NERR_InvalidComputer
 NERR_NotPrimary
 NERR_UserNotFound
NET_API_STATUS NetUserEnum(LPWSTR lpwszServerName, DWORD dwLevel,
 DWORD dwFilter, LPBYTE *lplpbBuff, DWORD dwPrefMaxLen,
 LPDWORD lpdwEntriesRead, LPDWORD lpdwTotalEntries,
 LPDWORD lpdwResumeHandle)
ユーザーアカウントを列挙する

引数
LPWSTR lpwszServerName ... リモートサーバーの名前
 NULLポインタやNULL文字列はローカルマシン
DWORD dwLevel ... USER_INFO_n構造体のレベル(0〜3,10,11,20)
DWORD dwFilter ... 列挙するアカウントを指定する
 FILTER_TEMP_DUPLICATE_ACCOUNTS
 FILTER_NORMAL_ACCOUNT
 FILTER_INTERDOMAIN_TRUST_ACCOUNT
 FILTER_WORKSTATION_TRUST_ACCOUNT
 FILTER_SERVER_TRUST_ACCOUNT
LPBYTE *lplpbBuff ... USER_INFO_n構造体を取得するポインタ
 取得した領域は NetApiBufferFree で解放する必要がある
DWORD dwPrefMaxLen ... 返されるデータの最大長として望ましい値をバイト単位で指定する
LPDWORD lpdwEntriesRead ... 実際に列挙された要素数
LPDWORD lpdwTotalEntries ... 現在の再開位置から列挙されるはずだったエントリの総数
LPDWORD lpdwResumeHandle ... 既存のユーザーの検索を続けるために使われる.
 不要な場合NULLを指定する

戻り値
正常終了 NERR_Success
異常終了 ERROR_ACCESS_DENIED
 ERROR_MORE_DATA
 NERR_InvalidComputer
NetUserChangePasswordユーザーのパスワードを変更
NetUserGetGroups ユーザーが属しているグローバルグループのリストを取得
NetUserGetInfo ユーザーアカウントに関する情報を取得
NetUserGetLocalGroups ユーザーが属しているローカルグループのリストを取得
NetUserSetGroups アカウントに対するグローバルなグループメンバの資格を設定
NetUserSetInfo ユーザーアカウントのパラメータを設定
NetUserModalsGet すべてのユーザーとグローバルなグループのグローバルな情報を取得
NetUserModalsSet すべてのユーザーとグローバルなグループのグローバルな情報を設定

リスト14-1:USER_INFO_1 構造体

typedef struct _USER_INFO_1 {
    LPWSTR    usri1_name;          アカウント名
    LPWSTR    usri1_password;      パスワード
    DWORD     usri1_password_age;  パスワードが変更されて経過した時刻[sec]
    DWORD     usri1_priv;          グループを示す NetUserAddは必ず
                                   USER_PRIV_USERを指定する
                                   USER_PRIV_GUEST ... Guest
                                   USER_PRIV_USER  ... User
                                   USER_PRIV_ADMIN ... Administrator
    LPWSTR    usri1_home_dir;      ホームディレクトリ
    LPWSTR    usri1_comment;       コメント
    DWORD     usri1_flags;         アカウントフラグ
                 UF_SCRIPT  LAN Manager 2.0 または Windows NTでは必ず設定する 
                 UF_ACCOUNTDISABLE  ユーザアカウントを無効にする 
                 UF_HOMEDIR_REQUIRED  WindowsNTでは無視される
                 UF_PASSWD_NOTREQD  パスワードを設定しない
                 UF_PASSWD_CANT_CHANGE  ユーザにパスワードを変更させない
                 UF_DONT_EXPIRE_PASSWD  無期限パスワード.WindowsNTのみ有効
                 UF_NORMAL_ACCOUNT  通常のユーザ
                 UF_LOCKOUT
                 UF_TEMP_DUPLICATE_ACCOUNT
                 UF_SERVER_TRUST_ACCOUNT
                 UF_WORKSTATION_TRUST_ACCOUNT
                 UF_INTERDOMAIN_TRUST_ACCOUNT
    LPWSTR    usri1_script_path;   スクリプトパス
} USER_INFO_1, *PUSER_INFO_1, *LPUSER_INFO_1; 

表14-2:ローカルグループ操作関数
NET_API_STATUS NET_API_FUNCTION NetLocalGroupGetMembers(
 LPWSTR lpwszServerName, LPWSTR lpwszGroupName, DWORD dwLevel,
 LPBYTE *lplpbBuf, DWORD dwPrefMaxLen, LPDWORD lpdwEntriesRead,
 LPDWORD lpdwTotalEntries, LPDWORD lpdwResumeHandle);
ローカルグループのアカウントを取得する

引数
LPWSTR lpwszServerName ... リモートサーバーの名前
 NULLポインタやNULL文字列はローカルマシン
DWORD dwLevel ... LOCALGROUP_MEMBERS_INFO_n構造体のレベル(0〜3)
LPBYTE *lplpbBuff ... LOCALGROUP_MEMBERS_INFO_n構造体を取得するポインタ
 取得した領域は NetApiBufferFree で解放する必要がある
DWORD dwPrefMaxLen ... 返されるデータの最大長として望ましい値をバイト単位で指定する
LPDWORD lpdwEntriesRead ... 実際に列挙された要素数
LPDWORD lpdwTotalEntries ... 現在の再開位置から列挙されるはずだったエントリの総数
LPDWORD lpdwResumeHandle ... 既存のユーザーの検索を続けるために使われる.
 不要な場合NULLを指定する

戻り値
正常終了 NERR_Success
異常終了 ERROR_ACCESS_DENIED
 ERROR_NO_SUCH_ALIAS
 NERR_InvalidComputer
NET_API_STATUS NET_API_FUNCTION NetLocalGroupSetMembers(
 LPWSTR lpwszServerName, LPWSTR lpwszGroupName, DWORD dwLevel,
 LPBYTE lpbBuf, DWORD dwTotalEntries)
ローカルグループのアカウントを取得する

引数
LPWSTR lpwszServerName ... リモートサーバーの名前
 NULLポインタやNULL文字列はローカルマシン
DWORD dwLevel ... LOCALGROUP_MEMBERS_INFO_n構造体のレベル(0〜3)
LPBYTE lpbBuff ... LOCALGROUP_MEMBERS_INFO_n構造体を指すポインタ
 取得した領域は NetApiBufferFree で解放する必要がある
DWORD dwTotalEntries ... 現在の再開位置から列挙されるはずだったエントリの総数

戻り値
正常終了 NERR_Success
異常終了 ERROR_NO_SUCH_MEMBER
 ERROR_INVALID_MEMBER
 NERR_GroupNotFound
NetLocalGroupAdd ローカルグループを作成
NetLocalGroupAddMembers ローカルグループに既存のアカウントまたはグローバルグループの メンバの資格を与える
NetLocalGroupDel ローカルグループアカウントとそのすべてのメンバを削除
NetLocalGroupDelMembers 特定のローカルグループからメンバを削除
NetLocalGroupEnum ローカルグループアカウントに関する情報を取得
NetLocalGroupGetInfo サーバー上の特定のローカルグループアカウントに関する情報を取得
NetLocalGroupSetInfo ローカルグループアカウントのパラメータを設定

リスト14-1:LOCALGROUP_MEMBERS_INFO_0 構造体

typedef struct _LOCALGROUP_MEMBERS_INFO_0 {
     PSID  lgrmi0_sid;   ... 設定するユーザのSID
} LOCALGROUP_MEMBERS_INFO_0; 

14-3 UNICODE版アプリケーションを作成する場合の注意点

今回紹介したセキュリティアカウントAPIは,UNICODE版のみサポートしていますが, UNICODE版アプリケーションはいまだマイナーな存在といえます.
アプリケーションのほとんどはWindows95/WindowsNTの両方に対応したものを作られる ことが多いと思います.この場合,ANSI版だけでも問題ありませんが,WindowsCEの 文字コードはUNICODEしか対応していません.近い将来,WindowsCEも対応する必要が あるアプリケーションを開発しているのであれば,UNICODE版もビルドできるように しておくと便利になるのではないかと思います.
そこで,UNICODEに対応したアプリケーションを作成するための注意点について簡単に まとめてみました.
UNICODE版アプリケーションを作成するには,WINDOWS.H をインクルードする前か, コンパイラのオプションで
#define UNICODE
#define _UNICODE
を定義しておく必要があります.
これらの定義がなければ,Win32 API はすべて末尾に "A" がついた関数がリンクされ, UNICODE版の場合は "W" がついた関数がリンクされます.ただし,UNICODE が定義 されている・されていないに関わらず,"A"のAPI, "W"のAPIを直接指定すれば呼び出す ことができます.
WindowsNTには,今回紹介したような UNICODE版しか存在しないAPIもあります. このようなAPIは,ヘッダファイルで関数名が変化することはありません. 変数の型もリンクされる関数と同様に,変化します.
<文字列へのポインタ LPTSTR>
通常 ... LPSTR (char*)
"UNICODE"を定義 ... LPWSTR (short*)
<固定文字列を TEXT("") マクロ,MFCでは_T("")マクロで文字列を囲む>
通常 ... ANKまたはシフトJIS文字列
"UNICODE"を定義 ... UNICODE文字列
例:TEXT("あいう")
<文字列の領域を確保 TCHAR>
通常 ... CHAR (char)
"UNICODE"を定義 ... WCHAR (short)
Cランタイムルーチンの関数ではこのようなトリックはなく,ANSI版 printf は, wprintf, strcpy は wcscpy を意図的に利用する必要があります.
そしてメイン関数は,
ANSI版 int main(int argc, char *argv[] char *env[])
UNICODE版 int wmain(int argc, wchar_t *argv[], wchar_t *env[])
と,異なる関数を定義する必要があります.
ANSI版のアプリケーションでUNICODEの固定文字列を定義するには,L""を利用 (例えば,L"あいう")します.
TEXT("")マクロやL""を利用していない文字列は,"UNICODE", "_UNICODE"が定義されて いる・いないに関わらず,ANSI文字列として扱われます.
ANSI文字列を,動的にUNICODE文字列に変換するには,Win32 API MultiByteToWideChar を利用します.
ANSI版のコードとUNICODE版のコードは,引き渡す引数の文字列が正しければ, お互いにリンクして利用することができます.
Windows95では,Win32 API MessageBox のみUNICODE版の MessageBoxW が正常に動作 します.これは,UNICODE版アプリケーションがWindows95上で実行されたときに警告を 表示できるようにするためではないかと思われます.
これらをうまく利用することで,ANSI版, UNICODE版両方でビルド可能なソースを 作成することができます.

例14-1:Win32 API MessageBox の定義

WINUSER.Hでの定義

WINUSERAPI int WINAPI MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
WINUSERAPI int WINAPI MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);

#ifdef UNICODE
#define MessageBox  MessageBoxW
#else
#define MessageBox  MessageBoxA
#endif

14-4 ユーザに権限を与えるには

ユーザーアカウントは通常,いずれかの組み込みグループに所属しています. ファイルやレジストリなどのオブジェクトは,ユーザーごとでなくグループごとに アクセス権を設定することによって,ユーザーの増減でアクセス権の設定を変更しなく ても済むようになっています.システムの動作を左右するような操作,例えばサービス の起動やシステムのシャットダウンなどは誰でも実行できるようにしておくと, システムの運用に問題が起こります.このため,組み込みグループにはあらかじめ システムの操作を行うことができる権限をランク分けして適当な権限を設定して あります.各ユーザやグループに設定されている権限は,ユーザーマネージャで 確認することができますが,Administrators グループに属しているユーザでさえ, すべての権限を持っている訳ではありません.これは,Administrators グループの ユーザーはあくまでも「システム管理者」であり,システムを無制限に使えるユーザで はなくシステムを管理するためのユーザーであるということです.
したがって,システムの管理に必要な最小限の権限しか持っていません.
Win32 API LogonUser のように,ユーザアカウントに特別な特権がなければ利用 できない API を利用するとき,個人的に使うときにはユーザマネージャで設定する すれば解決することができます.しかし,そのようなアプリケーションをユーザに 提供しなければならないときには,インストーラなどで自動的に設定させる必要が あります.しかし,特別なアクセス権をむやみに設定すると,その設定がセキュリティ ホールになる可能性があるため,不必要に設定を変更しないように注意しなければ なりません.
Windows3.51以降,PSAPI, IMAGEHELP, GINA のように以前はドライバーや一部の アプリケーションにのみ解放されていた,システムのセキュリティに関わる機能が 少しづつ公開されています.アプリケーションを開発する立場から言えば,WindowsNT を利用する用途がそれだけ広がる訳ですから大歓迎なのですが,使い方を誤ると深刻な バグやセキュリティホールを作り出す原因となりまねません.
LSA(Local Security Authority) APIも,WindowsNT3.51から公開された機能の1つ です.このAPIを利用することにより,ユーザーアカウントなどのローカルシステム セキュリティを操作するための手段を提供します.なお,LSAPI を利用するには, NTSECAPI.H をインクルード,ADVAPI32.LIB をリンクします.
1) Win32 LSAAPI LsaOpenPolicy でユーザの特権を設定できる特権を設定します
2) 設定するアカウントのSIDを取得します
3) Win32 LSAAPI LsaAddAccountRights, Win32 LSAPI LsaRemoveAccountRights で ユーザの特権を調整します
4) Win32 LSAAPI LsaClose で設定を終了します

14-5 プログラムについて

WindowsNTのユーザアカウントを追加・削除するアプリケーションです. このサンプルではローカルグループとユーザのみを操作することができます. 作成されたユーザアカウントには,Users グループを設定しています.
 ドメインに対応させるには,Win32 API NetGetDCName で取得したプライマリドメイン コントローラ名を設定することで可能ですが,ドメインコントローラがない環境で実行 すると,タイムアウトが発生するまで待ち状態になりますので,ローカルアカウントを 対象にしている場合は Win32 API NetGetDCName を呼び出さないほうがよい でしょう.
本サンプルでは,ローカルアカウントのみを対象にしていますので,ドメインに対応 する必要がある場合はドメイン名の取得処理を追加する必要があります.

●使い方
ユーザを追加する
>ADDUSR アカウント名
ユーザアカウントをグループに追加する
>ADDUSR アカウント名 GROUP ローカルグループ名
ユーザを削除する
>ADDUSR アカウント名 DELETE

●ソースファイル
ADDUSR.EXE
 ADDUSR.C(ソースファイル)
 MAKEFILE(メイクファイル)