C++の基礎 - ユーザとグループ
概要
LinuxをはじめとするUNIX系オペレーティングシステムでは、ユーザとグループの概念がシステムのセキュリティとアクセス制御の基盤となっている。
各ユーザには一意のユーザID (UID) が割り当てられ、各グループには一意のグループID (GID) が割り当てられる。
ユーザは1つ以上のグループに所属することができ、これによってファイルシステムのパーミッションやリソースアクセスが制御される。
C++プログラムからこれらのユーザおよびグループ情報にアクセスする必要がある場合、POSIX標準で定義されているシステムコールとライブラリ関数を使用する。
以下に示すような用途でこれらのAPIが使用される。
- システム管理ツールの開発
- ファイルアクセス制御の実装
- プロセス権限の確認と管理
- セキュリティ監査ログの記録
- マルチユーザ環境でのリソース管理
※注意
UNIX系システム (Linux、BSD等) 専用であり、Windowsでは動作しない。
Windows環境では、Windows APIを使用した別の実装が必要となる。
参考文献を以下に示す。
- POSIX.1-2017 Standard
- Linux Man Pages:
使用する主なPOSIX API関数
ユーザとグループの情報を取得するため、POSIX API関数が使用される。
ユーザ情報取得関数
- getuid()
- 現在のプロセスの実行ユーザID (UID) を取得する。
- 戻り値は、
uid_t型である。
- getpwuid(uid_t uid)
- 指定されたUIDに対応するユーザ情報を取得する。
struct passwdへのポインタを返す。
- getpwnam(const char *name)
- 指定されたユーザ名に対応するユーザ情報を取得する。
struct passwdへのポインタを返す。
グループ情報取得関数
- getgid()
- 現在のプロセスの実行グループID (GID) を取得する。
- 戻り値は、
gid_t型である。
- getgrgid(gid_t gid)
- 指定されたGIDに対応するグループ情報を取得する。
struct groupへのポインタを返す。
- getgrnam(const char *name)
- 指定されたグループ名に対応するグループ情報を取得する。
struct groupへのポインタを返す。
- getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups)
- 指定されたユーザが所属する全てのグループのリストを取得する。
主要なデータ構造
struct passwd
ユーザ情報を格納する構造体である。
<pwd.h> で定義されている。
struct passwd {
char *pw_name; // ユーザ名
char *pw_passwd; // 暗号化されたパスワード (通常は使用されない)
uid_t pw_uid; // ユーザID
gid_t pw_gid; // プライマリグループID
char *pw_gecos; // ユーザ情報 (フルネーム等)
char *pw_dir; // ホームディレクトリ
char *pw_shell; // デフォルトシェル
};
struct group
グループ情報を格納する構造体である。
<grp.h> で定義されている。
struct group {
char *gr_name; // グループ名
char *gr_passwd; // 暗号化されたパスワード (通常は使用されない)
gid_t gr_gid; // グループID
char **gr_mem; // グループメンバーのユーザ名リスト (NULL終端)
};
現在のユーザ情報の取得
以下の例では、現在のユーザの詳細情報を取得している。
#include <iostream>
#include <string>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
struct UserInfo {
std::string username;
uid_t uid;
gid_t gid;
std::string homeDirectory;
std::string shell;
std::string fullName;
};
bool getCurrentUserInfo(UserInfo& userInfo, std::string& errorMessage)
{
errorMessage.clear();
// 現在のユーザのUIDを取得
uid_t uid = getuid();
// UIDからユーザ情報を取得
struct passwd *pw = getpwuid(uid);
if (!pw) {
errorMessage = "Failed to get user information for UID " + std::to_string(uid);
return false;
}
// 構造体に情報をコピー
userInfo.username = pw->pw_name;
userInfo.uid = pw->pw_uid;
userInfo.gid = pw->pw_gid;
userInfo.homeDirectory = pw->pw_dir;
userInfo.shell = pw->pw_shell;
userInfo.fullName = pw->pw_gecos;
return true;
}
// 使用例
int main()
{
UserInfo userInfo;
std::string errorMessage;
if (getCurrentUserInfo(userInfo, errorMessage)) {
std::cout << "=== Current User Information ===" << std::endl;
std::cout << "Username: " << userInfo.username << std::endl;
std::cout << "UID: " << userInfo.uid << std::endl;
std::cout << "GID: " << userInfo.gid << std::endl;
std::cout << "Home Directory: " << userInfo.homeDirectory << std::endl;
std::cout << "Shell: " << userInfo.shell << std::endl;
std::cout << "Full Name: " << userInfo.fullName << std::endl;
}
else {
std::cerr << "Error: " << errorMessage << std::endl;
return 1;
}
return 0;
}
Linuxのグループの取得
以下の例では、現在のユーザが所属しているグループ名を取得している。
標準C++とPOSIX APIのみを使用している。
ただし、これはUNIX系システム (Linux、BSD等) 向けであるため、Windowsでは動作しないことに注意する。
Windowsで同様の機能を実装する場合は、Windows APIを使用する必要がある。
以下に示す手順で動作する。
- まず、現在のユーザのUIDを取得する。
- UIDを使用してユーザ情報を取得する。
- getgrouplist関数 (UNIX系システム専用) を使用して、ユーザが所属するグループの数とIDを取得する。
- 各グループIDに対して、getgrgid関数を使用してグループ名を取得する。
- 取得したグループ名を std::vector< std::string> クラスに追加する。
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <grp.h>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
std::vector<std::string> getUserGroups(std::string& errorMessage)
{
std::vector<std::string> groups;
errorMessage.clear();
// 現在のユーザのUIDを取得
uid_t uid = getuid();
// UIDからユーザ情報を取得
struct passwd *pw = getpwuid(uid);
if (!pw) {
errorMessage = "Failed to get user info: " + std::string(strerror(errno));
return groups;
}
// まずグループ数を取得 (最初の呼び出しで必要なバッファサイズを取得)
int ngroups = 0;
if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &ngroups) == -1) {
// この失敗は期待される動作(サイズ取得のため)
if (ngroups <= 0) {
errorMessage = "Failed to get group list size";
return groups;
}
}
// グループIDを格納するバッファを確保
if (ngroups > 0) {
std::vector<gid_t> gids(ngroups);
// 実際のグループリストを取得
if (getgrouplist(pw->pw_name, pw->pw_gid, gids.data(), &ngroups) == -1) {
errorMessage = "Failed to get group list";
return groups;
}
// 各グループIDからグループ名を取得
for (int i = 0; i < ngroups; i++) {
errno = 0;
struct group *gr = getgrgid(gids[i]);
if (gr) {
groups.push_back(std::string(gr->gr_name));
}
else {
// エラーを記録するが、処理は継続
std::cerr << "Failed to get group name for gid " << gids[i]
<< ": " << strerror(errno) << std::endl;
}
}
}
else {
errorMessage = "User is not a member of any groups";
}
return groups;
}
// 使用例
int main()
{
std::string errorMessage;
std::vector<std::string> userGroups = getUserGroups(errorMessage);
if (!errorMessage.empty()) {
std::cerr << "Error occurred: " << errorMessage << std::endl;
return 1;
}
if (userGroups.empty()) {
std::cout << "User is not a member of any groups" << std::endl;
}
else {
std::cout << "User groups (" << userGroups.size() << " total):" << std::endl;
for (const auto& group : userGroups) {
std::cout << " " << group << std::endl;
}
}
return 0;
}
特定グループのメンバー取得
以下の例では、指定されたグループ名に所属する全メンバーのユーザ名を取得している。
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <grp.h>
#include <errno.h>
std::vector<std::string> getGroupMembers(const std::string& groupName, std::string& errorMessage)
{
std::vector<std::string> members;
errorMessage.clear();
// グループ名からグループ情報を取得
errno = 0;
struct group *gr = getgrnam(groupName.c_str());
if (!gr) {
if (errno != 0) {
errorMessage = "Failed to get group '" + groupName + "': " + std::string(strerror(errno));
}
else {
errorMessage = "Group '" + groupName + "' not found";
}
return members;
}
// メンバーリストをstd::vectorにコピー
// gr_memはNULL終端の文字列ポインタ配列
char **member = gr->gr_mem;
while (*member != nullptr) {
members.push_back(std::string(*member));
member++;
}
return members;
}
// 使用例
int main()
{
std::string groupName = "sudo"; // 例:sudoグループ
std::string errorMessage;
std::vector<std::string> members = getGroupMembers(groupName, errorMessage);
if (!errorMessage.empty()) {
std::cerr << "Error: " << errorMessage << std::endl;
return 1;
}
std::cout << "Members of group '" << groupName << "' (" << members.size() << " total):" << std::endl;
if (members.empty()) {
std::cout << " (No members)" << std::endl;
}
else {
for (const auto& member : members) {
std::cout << " " << member << std::endl;
}
}
return 0;
}
特定ユーザの詳細情報取得
以下の例では、ユーザ名を指定して、そのユーザの詳細情報を取得している。
#include <iostream>
#include <string>
#include <vector>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <cstring>
struct DetailedUserInfo {
std::string username;
uid_t uid;
gid_t primaryGid;
std::string primaryGroupName;
std::string homeDirectory;
std::string shell;
std::string fullName;
std::vector<std::string> groups;
};
bool getUserInfo(const std::string& username, DetailedUserInfo& info, std::string& errorMessage)
{
errorMessage.clear();
// ユーザ名からユーザ情報を取得
errno = 0;
struct passwd *pw = getpwnam(username.c_str());
if (!pw) {
if (errno != 0) {
errorMessage = "Failed to get user '" + username + "': " + std::string(strerror(errno));
}
else {
errorMessage = "User '" + username + "' not found";
}
return false;
}
// 基本情報を設定
info.username = pw->pw_name;
info.uid = pw->pw_uid;
info.primaryGid = pw->pw_gid;
info.homeDirectory = pw->pw_dir;
info.shell = pw->pw_shell;
info.fullName = pw->pw_gecos;
// プライマリグループ名を取得
struct group *gr = getgrgid(pw->pw_gid);
if (gr) {
info.primaryGroupName = gr->gr_name;
}
// ユーザが所属する全グループを取得
int ngroups = 0;
if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &ngroups) == -1) {
if (ngroups > 0) {
std::vector<gid_t> gids(ngroups);
if (getgrouplist(pw->pw_name, pw->pw_gid, gids.data(), &ngroups) != -1) {
for (int i = 0; i < ngroups; i++) {
struct group *g = getgrgid(gids[i]);
if (g) {
info.groups.push_back(std::string(g->gr_name));
}
}
}
}
}
return true;
}
// 使用例
int main()
{
std::string username = "root"; // 例:rootユーザ
DetailedUserInfo info;
std::string errorMessage;
if (getUserInfo(username, info, errorMessage)) {
std::cout << "=== User Information for '" << username << "' ===" << std::endl;
std::cout << "Username: " << info.username << std::endl;
std::cout << "UID: " << info.uid << std::endl;
std::cout << "Primary GID: " << info.primaryGid << std::endl;
std::cout << "Primary Group: " << info.primaryGroupName << std::endl;
std::cout << "Home Directory: " << info.homeDirectory << std::endl;
std::cout << "Shell: " << info.shell << std::endl;
std::cout << "Full Name: " << info.fullName << std::endl;
std::cout << "\nGroup Memberships (" << info.groups.size() << "):" << std::endl;
for (const auto& group : info.groups) {
std::cout << " " << group << std::endl;
}
}
else {
std::cerr << "Error: " << errorMessage << std::endl;
return 1;
}
return 0;
}
エラーハンドリング
errnoの適切な使用
多くのPOSIX関数は、エラー発生時に errno グローバル変数にエラーコードを設定する。
ただし、成功時には errno をクリアしないため、使用前に必ず0に初期化すること。
errno = 0; // 使用前にクリア
struct passwd *pw = getpwuid(uid);
if (!pw) {
if (errno != 0) {
std::cerr << "Error: " << strerror(errno) << std::endl;
}
else {
std::cerr << "User not found" << std::endl;
}
}
スレッドセーフ関数の使用
マルチスレッド環境では、再入可能 (リエントラント) な関数を使用すること。
POSIX標準では、多くの関数に対して _r サフィックスを持つスレッドセーフ版が提供されている。
// スレッドセーフ版の例
struct passwd pwd;
struct passwd *result;
char buffer[16384]; // 十分なバッファサイズを確保
int ret = getpwuid_r(uid, &pwd, buffer, sizeof(buffer), &result);
if (ret != 0) {
std::cerr << "Error: " << strerror(ret) << std::endl;
}
else if (result == nullptr) {
std::cerr << "User not found" << std::endl;
}
else {
std::cout << "Username: " << result->pw_name << std::endl;
}
セキュリティの考慮
権限の確認
ユーザやグループ情報の読み取りには特別な権限は不要だが、一部の機密情報 (暗号化されたパスワード等) へのアクセスは制限されている可能性がある。
Time-of-check to time-of-use (TOCTOU) 攻撃への対策
ユーザ情報の確認から実際の使用までの間に、システムの状態が変化する可能性がある。
セキュリティクリティカルな操作では、この事柄に注意すること。
入力の検証
外部から受け取ったユーザ名やグループ名を使用する場合は、必ず検証を行うこと。
特殊文字や異常な長さの入力によるセキュリティ問題を防ぐため、サニタイズ処理を行うこと。