[PHP]posix_getgid関数の使い方を完全解説!実グループIDを取得する方法

PHP

はじめに

PHPでシステムプログラミングを行う際、プロセスがどのグループに属しているかを知ることは、権限管理やセキュリティ実装において非常に重要です。

Unixライクなシステムでは、プロセスは実グループID実効グループIDという2つのグループIDを持ちます。実グループIDは、プロセスを起動したユーザーが本来属しているグループを表します。

その実グループIDを取得するのがposix_getgid関数です。この関数を使えば、プロセスの基本的なグループ所属情報を正確に把握できます。

この記事では、posix_getgidの基本から実践的な活用方法まで、詳しく解説します。

posix_getgidとは?

posix_getgidは、現在のプロセスの実グループID(Real Group ID)を返す関数です。

基本構文

posix_getgid(): int

パラメータ

この関数はパラメータを取りません。

戻り値

  • 整数: 実グループID(GID)

実グループIDと実効グループIDの違い

種類関数説明変更可能
実グループID (RGID)posix_getgid()プロセスを起動したユーザーの本来のグループ基本的に変更不可
実効グループID (EGID)posix_getegid()ファイルアクセス権限チェックに使用されるグループ条件付きで変更可能

通常は両者は同じですが、setgidビットが設定されたプログラムを実行すると異なる場合があります。

対応環境

  • POSIX準拠システム(Linux、Unix、macOS)
  • Windows では利用不可
  • POSIX拡張モジュールが必要

対応バージョン

  • PHP 4.0.0 以降で使用可能

基本的な使い方

実グループIDの取得

<?php
$gid = posix_getgid();
echo "実グループID: {$gid}\n";

// グループ名を取得
$group = posix_getgrgid($gid);
if ($group) {
    echo "グループ名: {$group['name']}\n";
    echo "メンバー: " . implode(', ', $group['members']) . "\n";
}

// 出力例:
// 実グループID: 1000
// グループ名: user
// メンバー: user
?>

実効グループIDとの比較

<?php
function compareGroupIDs() {
    $rgid = posix_getgid();   // 実グループID
    $egid = posix_getegid();  // 実効グループID

    echo "=== グループID比較 ===\n\n";

    // 実グループID
    $real_group = posix_getgrgid($rgid);
    echo "実グループID (RGID): {$rgid}\n";
    echo "  グループ名: {$real_group['name']}\n";

    // 実効グループID
    $eff_group = posix_getgrgid($egid);
    echo "\n実効グループID (EGID): {$egid}\n";
    echo "  グループ名: {$eff_group['name']}\n";

    // 比較
    echo "\n状態: ";
    if ($rgid === $egid) {
        echo "同じ(通常の状態)\n";
    } else {
        echo "異なる(setgidプログラム実行中)\n";
        echo "  実グループ: {$real_group['name']}\n";
        echo "  実効グループ: {$eff_group['name']}\n";
    }
}

compareGroupIDs();
?>

すべてのグループ情報の取得

<?php
function displayAllGroupInfo() {
    echo "=== グループ所属情報 ===\n\n";

    // 実グループID
    $rgid = posix_getgid();
    $real_group = posix_getgrgid($rgid);
    echo "実グループ: {$real_group['name']} (GID: {$rgid})\n";

    // 実効グループID
    $egid = posix_getegid();
    $eff_group = posix_getgrgid($egid);
    echo "実効グループ: {$eff_group['name']} (GID: {$egid})\n";

    // 補助グループ
    $groups = posix_getgroups();
    echo "\n補助グループ:\n";

    if ($groups) {
        foreach ($groups as $gid) {
            $group = posix_getgrgid($gid);
            $marker = '';

            if ($gid === $rgid) {
                $marker = ' [実グループ]';
            } elseif ($gid === $egid) {
                $marker = ' [実効グループ]';
            }

            echo "  - {$group['name']} (GID: {$gid}){$marker}\n";
        }
    } else {
        echo "  (なし)\n";
    }
}

displayAllGroupInfo();
?>

実践的な使用例

例1: プロセス識別情報の完全表示

<?php
class ProcessIdentity {
    /**
     * プロセスの完全な識別情報を表示
     */
    public static function display() {
        echo "=== プロセス識別情報 ===\n";
        echo "日時: " . date('Y-m-d H:i:s') . "\n\n";

        // ユーザー情報
        $ruid = posix_getuid();
        $euid = posix_geteuid();

        $real_user = posix_getpwuid($ruid);
        $eff_user = posix_getpwuid($euid);

        echo "【ユーザー】\n";
        echo "  実ユーザー: {$real_user['name']} (UID: {$ruid})\n";
        echo "  実効ユーザー: {$eff_user['name']} (UID: {$euid})\n";

        // グループ情報
        $rgid = posix_getgid();
        $egid = posix_getegid();

        $real_group = posix_getgrgid($rgid);
        $eff_group = posix_getgrgid($egid);

        echo "\n【グループ】\n";
        echo "  実グループ: {$real_group['name']} (GID: {$rgid})\n";
        echo "  実効グループ: {$eff_group['name']} (GID: {$egid})\n";

        // 補助グループ
        $groups = posix_getgroups();
        echo "\n【補助グループ】\n";
        if ($groups) {
            $group_names = [];
            foreach ($groups as $gid) {
                $group = posix_getgrgid($gid);
                $group_names[] = "{$group['name']}({$gid})";
            }
            echo "  " . implode(', ', $group_names) . "\n";
        } else {
            echo "  (なし)\n";
        }

        // プロセスID
        echo "\n【プロセス】\n";
        echo "  PID: " . posix_getpid() . "\n";
        echo "  親PID: " . posix_getppid() . "\n";
        echo "  プロセスグループ: " . posix_getpgid(posix_getpid()) . "\n";

        // 権限の変更チェック
        echo "\n【権限状態】\n";
        if ($ruid !== $euid || $rgid !== $egid) {
            echo "  ⚠ 権限が変更されています\n";

            if ($ruid !== $euid) {
                echo "    - ユーザーID変更あり(setuid実行)\n";
            }
            if ($rgid !== $egid) {
                echo "    - グループID変更あり(setgid実行)\n";
            }
        } else {
            echo "  ✓ 通常の権限で実行中\n";
        }
    }
}

ProcessIdentity::display();
?>

例2: グループメンバーシップの確認

<?php
class GroupMembership {
    /**
     * 指定したグループのメンバーか確認
     */
    public static function isMemberOf($group_name) {
        // グループ情報を取得
        $group = posix_getgrnam($group_name);

        if (!$group) {
            return [
                'member' => false,
                'reason' => 'グループが存在しません'
            ];
        }

        $target_gid = $group['gid'];

        // 実グループIDをチェック
        $rgid = posix_getgid();
        if ($rgid === $target_gid) {
            return [
                'member' => true,
                'type' => 'primary',
                'reason' => '実グループ(プライマリグループ)として所属'
            ];
        }

        // 実効グループIDをチェック
        $egid = posix_getegid();
        if ($egid === $target_gid) {
            return [
                'member' => true,
                'type' => 'effective',
                'reason' => '実効グループとして所属'
            ];
        }

        // 補助グループをチェック
        $groups = posix_getgroups();
        if (in_array($target_gid, $groups)) {
            return [
                'member' => true,
                'type' => 'supplementary',
                'reason' => '補助グループとして所属'
            ];
        }

        return [
            'member' => false,
            'reason' => 'グループに所属していません'
        ];
    }

    /**
     * ユーザーのグループメンバーシップを解析
     */
    public static function analyzeUserGroups($username = null) {
        // ユーザー情報を取得
        if ($username) {
            $user = posix_getpwnam($username);
            if (!$user) {
                echo "ユーザーが存在しません: {$username}\n";
                return;
            }
            $uid = $user['uid'];
            $primary_gid = $user['gid'];
        } else {
            $uid = posix_getuid();
            $user = posix_getpwuid($uid);
            $primary_gid = posix_getgid();
        }

        echo "=== グループメンバーシップ分析 ===\n";
        echo "ユーザー: {$user['name']} (UID: {$uid})\n\n";

        // プライマリグループ
        $primary_group = posix_getgrgid($primary_gid);
        echo "プライマリグループ:\n";
        echo "  {$primary_group['name']} (GID: {$primary_gid})\n";

        // すべてのグループを取得
        if ($username) {
            // 他のユーザーの場合は/etc/groupから取得
            $all_groups = self::getUserGroupsFromEtcGroup($username);
        } else {
            // 現在のユーザーの場合
            $all_groups = posix_getgroups();
        }

        echo "\nすべての所属グループ:\n";
        foreach ($all_groups as $gid) {
            $group = posix_getgrgid($gid);
            $type = ($gid === $primary_gid) ? ' [プライマリ]' : ' [補助]';
            echo "  - {$group['name']} (GID: {$gid}){$type}\n";
        }
    }

    private static function getUserGroupsFromEtcGroup($username) {
        $groups = [];
        $file = fopen('/etc/group', 'r');

        if ($file) {
            while (($line = fgets($file)) !== false) {
                $parts = explode(':', trim($line));
                if (count($parts) >= 4) {
                    $members = explode(',', $parts[3]);
                    if (in_array($username, $members)) {
                        $groups[] = (int)$parts[2];
                    }
                }
            }
            fclose($file);
        }

        return $groups;
    }
}

// 使用例
echo "現在のユーザーのグループ所属:\n";
$result = GroupMembership::isMemberOf('www-data');
echo "www-dataグループ: " . ($result['member'] ? 'はい' : 'いいえ') . "\n";
echo "詳細: {$result['reason']}\n\n";

GroupMembership::analyzeUserGroups();
?>

例3: ファイルのグループ所有権チェック

<?php
class FileGroupChecker {
    /**
     * ファイルのグループ所有権を確認
     */
    public static function checkFileGroup($filepath) {
        if (!file_exists($filepath)) {
            echo "ファイルが存在しません: {$filepath}\n";
            return false;
        }

        echo "=== ファイルグループチェック ===\n";
        echo "ファイル: {$filepath}\n\n";

        // ファイルのグループID
        $file_stat = stat($filepath);
        $file_gid = $file_stat['gid'];
        $file_group = posix_getgrgid($file_gid);

        echo "ファイルのグループ: {$file_group['name']} (GID: {$file_gid})\n\n";

        // プロセスのグループID
        $rgid = posix_getgid();
        $egid = posix_getegid();

        $real_group = posix_getgrgid($rgid);
        $eff_group = posix_getgrgid($egid);

        echo "プロセスのグループ:\n";
        echo "  実グループ: {$real_group['name']} (GID: {$rgid})\n";
        echo "  実効グループ: {$eff_group['name']} (GID: {$egid})\n\n";

        // 一致チェック
        $real_match = ($rgid === $file_gid);
        $eff_match = ($egid === $file_gid);

        echo "グループ一致:\n";
        echo "  実グループ: " . ($real_match ? '✓ 一致' : '✗ 不一致') . "\n";
        echo "  実効グループ: " . ($eff_match ? '✓ 一致' : '✗ 不一致') . "\n";

        // 補助グループもチェック
        $groups = posix_getgroups();
        $in_supplementary = in_array($file_gid, $groups);

        if ($in_supplementary) {
            echo "  補助グループ: ✓ ファイルグループに所属\n";
        }

        // アクセス権限
        $perms = $file_stat['mode'];
        $group_read = ($perms & 0040) !== 0;
        $group_write = ($perms & 0020) !== 0;
        $group_exec = ($perms & 0010) !== 0;

        echo "\nグループパーミッション:\n";
        echo "  読み取り: " . ($group_read ? '✓' : '✗') . "\n";
        echo "  書き込み: " . ($group_write ? '✓' : '✗') . "\n";
        echo "  実行: " . ($group_exec ? '✓' : '✗') . "\n";

        // アクセス可否判定
        $can_access = $eff_match || $in_supplementary;

        echo "\n結果: ";
        if ($can_access) {
            echo "グループ権限でアクセス可能\n";
        } else {
            echo "グループ権限ではアクセス不可\n";
        }

        return $can_access;
    }
}

// 使用例
FileGroupChecker::checkFileGroup('/var/www/html/index.php');
?>

例4: グループベースのアクセス制御

<?php
class GroupAccessControl {
    private $required_groups = [];

    /**
     * 必要なグループを追加
     */
    public function requireGroup($group_name) {
        $group = posix_getgrnam($group_name);

        if (!$group) {
            throw new InvalidArgumentException(
                "グループが存在しません: {$group_name}"
            );
        }

        $this->required_groups[] = [
            'gid' => $group['gid'],
            'name' => $group['name']
        ];

        return $this;
    }

    /**
     * アクセス可否をチェック
     */
    public function checkAccess() {
        if (empty($this->required_groups)) {
            return [
                'allowed' => true,
                'reason' => 'グループ制限なし'
            ];
        }

        $rgid = posix_getgid();
        $egid = posix_getegid();
        $groups = posix_getgroups();

        // すべてのグループIDを集める
        $all_gids = array_unique(array_merge([$rgid, $egid], $groups));

        // いずれかの必要グループに所属しているかチェック
        foreach ($this->required_groups as $req_group) {
            if (in_array($req_group['gid'], $all_gids)) {
                // グループタイプを判定
                if ($req_group['gid'] === $rgid) {
                    $type = '実グループ';
                } elseif ($req_group['gid'] === $egid) {
                    $type = '実効グループ';
                } else {
                    $type = '補助グループ';
                }

                return [
                    'allowed' => true,
                    'reason' => "'{$req_group['name']}' に{$type}として所属",
                    'group' => $req_group['name']
                ];
            }
        }

        $required_names = array_column($this->required_groups, 'name');

        return [
            'allowed' => false,
            'reason' => '必要なグループに所属していません',
            'required_groups' => $required_names
        ];
    }

    /**
     * アクセスを要求(拒否時は例外)
     */
    public function requireAccess() {
        $result = $this->checkAccess();

        if (!$result['allowed']) {
            $groups = implode(', ', $result['required_groups']);
            throw new RuntimeException(
                "アクセスが拒否されました。\n" .
                "必要なグループ: {$groups}\n" .
                "理由: {$result['reason']}"
            );
        }

        return $result;
    }

    /**
     * 保護された操作を実行
     */
    public function execute(callable $callback) {
        $access = $this->requireAccess();

        echo "✓ アクセス許可: {$access['reason']}\n";

        return $callback();
    }
}

// 使用例
$acl = new GroupAccessControl();
$acl->requireGroup('www-data')
    ->requireGroup('developers')
    ->requireGroup('admin');

try {
    $result = $acl->execute(function() {
        echo "保護されたリソースにアクセスしています...\n";
        return "処理完了";
    });

    echo "結果: {$result}\n";
} catch (RuntimeException $e) {
    echo "アクセスエラー:\n";
    echo $e->getMessage() . "\n";
}
?>

例5: グループ情報のレポート生成

<?php
class GroupReport {
    /**
     * システムのグループ情報レポートを生成
     */
    public static function generateSystemReport() {
        echo "=== システムグループレポート ===\n";
        echo "生成日時: " . date('Y-m-d H:i:s') . "\n\n";

        // 現在のプロセスの情報
        $rgid = posix_getgid();
        $egid = posix_getegid();

        echo "【現在のプロセス】\n";
        echo "  実グループID: {$rgid}\n";
        echo "  実効グループID: {$egid}\n\n";

        // すべてのグループを取得
        $all_groups = [];
        $groups = posix_getgroups();

        foreach ($groups as $gid) {
            $group = posix_getgrgid($gid);
            $all_groups[] = [
                'gid' => $gid,
                'name' => $group['name'],
                'members' => $group['members'],
                'is_real' => ($gid === $rgid),
                'is_effective' => ($gid === $egid)
            ];
        }

        // ソート
        usort($all_groups, fn($a, $b) => $a['gid'] <=> $b['gid']);

        echo "【所属グループ一覧】\n";
        echo sprintf("%-6s %-20s %-10s %s\n", "GID", "グループ名", "タイプ", "メンバー数");
        echo str_repeat("-", 70) . "\n";

        foreach ($all_groups as $group) {
            $type_marks = [];
            if ($group['is_real']) $type_marks[] = '実';
            if ($group['is_effective']) $type_marks[] = '実効';
            if (empty($type_marks)) $type_marks[] = '補助';

            $type = implode(',', $type_marks);
            $member_count = count($group['members']);

            echo sprintf(
                "%-6d %-20s %-10s %d\n",
                $group['gid'],
                substr($group['name'], 0, 20),
                $type,
                $member_count
            );
        }

        echo "\n総グループ数: " . count($all_groups) . "\n";
    }

    /**
     * グループの詳細情報を表示
     */
    public static function displayGroupDetails($group_name) {
        $group = posix_getgrnam($group_name);

        if (!$group) {
            echo "グループが存在しません: {$group_name}\n";
            return;
        }

        echo "=== グループ詳細: {$group_name} ===\n\n";
        echo "GID: {$group['gid']}\n";
        echo "グループ名: {$group['name']}\n";

        if (!empty($group['members'])) {
            echo "メンバー:\n";
            foreach ($group['members'] as $member) {
                echo "  - {$member}\n";
            }
        } else {
            echo "メンバー: (なし)\n";
        }

        // このグループに属するプロセスか確認
        $rgid = posix_getgid();
        $egid = posix_getegid();
        $groups = posix_getgroups();

        echo "\n現在のプロセス:\n";
        if ($rgid === $group['gid']) {
            echo "  ✓ 実グループとして所属\n";
        } elseif ($egid === $group['gid']) {
            echo "  ✓ 実効グループとして所属\n";
        } elseif (in_array($group['gid'], $groups)) {
            echo "  ✓ 補助グループとして所属\n";
        } else {
            echo "  ✗ このグループには所属していません\n";
        }
    }
}

// 使用例
GroupReport::generateSystemReport();
echo "\n";
GroupReport::displayGroupDetails('www-data');
?>

グループIDの変更

実グループIDは基本的に変更不可

<?php
// 実グループIDは通常変更できません
// (特権プロセスでも制限があります)

// 実効グループIDは条件付きで変更可能
$rgid = posix_getgid();
$egid = posix_getegid();

echo "変更前:\n";
echo "  実グループID: {$rgid}\n";
echo "  実効グループID: {$egid}\n";

// 実効グループIDの変更を試行(通常は失敗)
if (posix_setegid(33)) { // www-dataのGID
    echo "\n実効グループIDを変更しました\n";
    echo "  新しい実効GID: " . posix_getegid() . "\n";
} else {
    $errno = posix_get_last_error();
    $errmsg = posix_strerror($errno);
    echo "\n実効グループIDの変更に失敗: {$errmsg}\n";
}

// 注意: 実グループIDを変更するposix_setgid()は
// root権限があっても慎重に使用する必要があります
?>

まとめ

posix_getgidは、現在のプロセスの実グループIDを取得する関数です。

主な特徴:

  • ✅ 実グループID(RGID)を返す
  • ✅ プロセスを起動したユーザーの本来のグループ
  • ✅ グループメンバーシップの基本情報
  • ✅ 通常は変更されない固定値

実効グループIDとの違い:

  • 実グループID (posix_getgid) – プロセス起動時のユーザーのグループ
  • 実効グループID (posix_getegid) – 実際の権限チェックに使用

使用場面:

  • プロセスの本来のグループ所属確認
  • グループメンバーシップの検証
  • ファイルグループ所有権の比較
  • グループベースのアクセス制御

関連関数:

  • posix_getegid() – 実効グループIDを取得
  • posix_getgroups() – 補助グループIDを取得
  • posix_getgrgid() – GIDからグループ情報を取得
  • posix_getgrnam() – グループ名からグループ情報を取得
  • posix_setegid() – 実効グループIDを設定

重要なポイント:

  • 実グループIDは基本的に固定
  • 実効グループIDが実際の権限チェックに使用される
  • 補助グループも合わせて確認する
  • setgidプログラムで実グループIDと実効グループIDが異なる

この関数を理解して、より正確なグループベースの権限管理を実現しましょう!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!

タイトルとURLをコピーしました