DATA/_SERVER/server_ports.php
<?php
// 기본 환경 설정 파일 포함
include_once('./_common.php'); 

// ============================================================
// firewall.php - iptables 포트 관리 페이지
// ============================================================
session_start();

// ★ 접근 제한 (필요시 IP 체크 추가)
// if ($_SERVER['REMOTE_ADDR'] !== 'YOUR_IP') { http_response_code(403); exit; }

$message = '';
$msg_type = '';

// ============================================================
// POST 처리
// ============================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $action = $_POST['action'] ?? '';
    $port   = (int)($_POST['port'] ?? 0);
    $proto  = in_array($_POST['proto'] ?? '', ['tcp','udp']) ? $_POST['proto'] : 'tcp';
    $ip     = trim($_POST['ip'] ?? '');

    if ($port < 1 || $port > 65535) {
        $message = '유효하지 않은 포트입니다.';
        $msg_type = 'error';
    } else {
        if ($action === 'block') {
            // 포트 차단
            $cmd = "iptables -D INPUT -p {$proto} --dport {$port} -j ACCEPT 2>/dev/null; "
                 . "iptables -I INPUT 1 -p {$proto} --dport {$port} -j DROP 2>&1";
            exec($cmd, $out, $ret);
            $message = "포트 {$port}/{$proto} 차단 완료";
            $msg_type = 'success';

        } elseif ($action === 'open_all') {
            // 전체 허용
            $cmd = "iptables -D INPUT -p {$proto} --dport {$port} -j DROP 2>/dev/null; "
                 . "iptables -D INPUT -p {$proto} --dport {$port} -j ACCEPT 2>/dev/null; "
                 . "iptables -I INPUT 1 -p {$proto} --dport {$port} -j ACCEPT 2>&1";
            exec($cmd, $out, $ret);
            $message = "포트 {$port}/{$proto} 전체 허용";
            $msg_type = 'success';

        } elseif ($action === 'open_white') {
            // 화이트IP만 허용
            if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
                $message = '유효하지 않은 IP입니다.';
                $msg_type = 'error';
            } else {
                // 기존 규칙 제거 후 화이트IP만 허용 + 나머지 DROP
                $cmd = "iptables -D INPUT -p {$proto} --dport {$port} -j DROP 2>/dev/null; "
                     . "iptables -D INPUT -p {$proto} --dport {$port} -j ACCEPT 2>/dev/null; "
                     . "iptables -D INPUT -s {$ip} -p {$proto} --dport {$port} -j ACCEPT 2>/dev/null; "
                     . "iptables -I INPUT 1 -p {$proto} --dport {$port} -j DROP; "
                     . "iptables -I INPUT 1 -s {$ip} -p {$proto} --dport {$port} -j ACCEPT 2>&1";
                exec($cmd, $out, $ret);
                $message = "포트 {$port}/{$proto} → {$ip} 만 허용";
                $msg_type = 'success';
            }
        } elseif ($action === 'delete') {
            // 규칙 전체 삭제 (초기화)
            $cmd = "iptables -D INPUT -p {$proto} --dport {$port} -j DROP 2>/dev/null; "
                 . "iptables -D INPUT -p {$proto} --dport {$port} -j ACCEPT 2>/dev/null; "
                 . "iptables -D INPUT -p {$proto} --dport {$port} -j ACCEPT 2>/dev/null 2>&1";
            exec($cmd, $out, $ret);
            $message = "포트 {$port}/{$proto} 규칙 삭제 (기본값 복원)";
            $msg_type = 'success';
        }
    }
}

// ============================================================
// 현재 LISTEN 포트 목록 수집
// ============================================================
exec("ss -tlnp 2>/dev/null", $ss_out);
$listen_ports = [];
foreach ($ss_out as $line) {
    if (!preg_match('/LISTEN/', $line)) continue;
    // 포트 추출
    if (preg_match('/:(\d+)\s/', $line, $m)) {
        $p = (int)$m[1];
        // 프로세스명 추출
        $proc = '';
        if (preg_match('/\("([^"]+)"/', $line, $pm)) $proc = $pm[1];
        // 바인드 주소
        $bind = 'all';
        if (preg_match('/(\S+):' . $p . '/', $line, $bm)) {
            $addr = $bm[1];
            if ($addr !== '0.0.0.0' && $addr !== '*' && $addr !== '[::]' && $addr !== '::') {
                $bind = $addr;
            }
        }
        if (!isset($listen_ports[$p])) {
            $listen_ports[$p] = ['port' => $p, 'proc' => $proc, 'bind' => $bind, 'proto' => 'tcp'];
        }
    }
}
ksort($listen_ports);

// ============================================================
// 현재 iptables 규칙 수집
// ============================================================
exec("iptables -L INPUT -n --line-numbers 2>/dev/null", $ipt_out);
$ipt_rules = [];
foreach ($ipt_out as $line) {
    if (preg_match('/(\d+)\s+(ACCEPT|DROP|REJECT)\s+(\w+)\s+--\s+(\S+)\s+\S+.*dpt:(\d+)/', $line, $m)) {
        $port_n = (int)$m[5];
        $ipt_rules[$port_n][] = [
            'num'    => $m[1],
            'target' => $m[2],
            'proto'  => $m[3],
            'src'    => $m[4],
        ];
    }
}

// 포트별 상태 결정
function get_port_status($port, $ipt_rules) {
    if (!isset($ipt_rules[$port])) return 'default'; // iptables 규칙 없음
    foreach ($ipt_rules[$port] as $r) {
        if ($r['target'] === 'DROP') return 'blocked';
        if ($r['target'] === 'ACCEPT' && $r['src'] !== '0.0.0.0') return 'white';
        if ($r['target'] === 'ACCEPT') return 'open';
    }
    return 'default';
}
function get_white_ip($port, $ipt_rules) {
    if (!isset($ipt_rules[$port])) return '';
    foreach ($ipt_rules[$port] as $r) {
        if ($r['target'] === 'ACCEPT' && $r['src'] !== '0.0.0.0') return $r['src'];
    }
    return '';
}

// 헤더 부분 포함
include_once(G5_PATH.'/_head.php'); 
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>방화벽 포트 관리</title>
<link rel="stylesheet" type="text/css" href="./server_ports.css">
</head>
<body>

<div class="PageBox">

    <h1><i class="fa-solid fa-shield-halved"></i> 방화벽 포트 관리 <span><?= date('Y-m-d H:i:s') ?></span></h1>

    <?php if ($message): ?>
    <div class="msg <?= $msg_type ?>">&gt; <?= htmlspecialchars($message) ?></div>
    <?php endif; ?>

    <!-- ★ LISTEN 포트 목록 -->
    <div class="section-title">현재 LISTEN 포트</div>
    <table class="port-table">
    <thead>
    <tr>
        <th>PORT</th>
        <th>PROTO</th>
        <th>PROCESS</th>
        <th>BIND</th>
        <th>STATUS</th>
        <th>WHITE IP</th>
        <th>ACTION</th>
    </tr>
    </thead>
    <tbody>
    <?php foreach ($listen_ports as $p => $info):
        $status   = get_port_status($p, $ipt_rules);
        $white_ip = get_white_ip($p, $ipt_rules);
        $status_label = match($status) {
            'open'    => '전체허용',
            'blocked' => '차단',
            'white'   => '화이트IP',
            default   => '기본',
        };
    ?>
    <tr>
        <td><strong><?= $p ?></strong></td>
        <td><?= $info['proto'] ?></td>
        <td><?= htmlspecialchars($info['proc']) ?></td>
        <td><?= htmlspecialchars($info['bind']) ?></td>
        <td><span class="badge <?= $status ?>"><?= $status_label ?></span></td>
        <td><?= $white_ip ? htmlspecialchars($white_ip) : '-' ?></td>
        <td>
            <!-- 차단 -->
            <form class="inline-form" method="post">
                <input type="hidden" name="action" value="block">
                <input type="hidden" name="port" value="<?= $p ?>">
                <input type="hidden" name="proto" value="tcp">
                <button type="submit" class="btn btn-block">차단</button>
            </form>
            <!-- 전체허용 -->
            <form class="inline-form" method="post">
                <input type="hidden" name="action" value="open_all">
                <input type="hidden" name="port" value="<?= $p ?>">
                <input type="hidden" name="proto" value="tcp">
                <button type="submit" class="btn btn-open">전체허용</button>
            </form>
            <!-- 화이트IP -->
            <button class="btn btn-white" onclick="toggleWhite(<?= $p ?>)">화이트IP</button>
            <div class="white-form" id="wf_<?= $p ?>">
                <form method="post" style="display:flex;gap:6px;align-items:center;">
                    <input type="hidden" name="action" value="open_white">
                    <input type="hidden" name="port" value="<?= $p ?>">
                    <input type="hidden" name="proto" value="tcp">
                    <input type="text" class="white-input" name="ip" placeholder="xxx.xxx.xxx.xxx" value="<?= htmlspecialchars($white_ip) ?>">
                    <button type="submit" class="btn btn-white">적용</button>
                </form>
            </div>
            <!-- 규칙삭제 -->
            <form class="inline-form" method="post">
                <input type="hidden" name="action" value="delete">
                <input type="hidden" name="port" value="<?= $p ?>">
                <input type="hidden" name="proto" value="tcp">
                <button type="submit" class="btn btn-del">초기화</button>
            </form>
        </td>
    </tr>
    <?php endforeach; ?>
    </tbody>
    </table>

    <!-- ★ 포트 직접 추가 -->
    <div class="add-section">
        <h2>포트 직접 제어</h2>
        <form method="post">
            <div class="form-row">
                <div class="form-group">
                    <label>PORT</label>
                    <input type="number" name="port" min="1" max="65535" placeholder="8080">
                </div>
                <div class="form-group">
                    <label>PROTO</label>
                    <select name="proto">
                        <option value="tcp">tcp</option>
                        <option value="udp">udp</option>
                    </select>
                </div>
                <div class="form-group">
                    <label>WHITE IP (화이트IP 선택시만)</label>
                    <input type="text" name="ip" placeholder="xxx.xxx.xxx.xxx">
                </div>
                <div class="form-group">
                    <label>ACTION</label>
                    <select name="action">
                        <option value="block">차단</option>
                        <option value="open_all">전체허용</option>
                        <option value="open_white">화이트IP만 허용</option>
                        <option value="delete">규칙삭제(초기화)</option>
                    </select>
                </div>
                <div class="form-group">
                    <label>&nbsp;</label>
                    <button type="submit" class="btn btn-open">실행</button>
                </div>
            </div>
        </form>
    </div>

    <!-- ★ iptables 현재 상태 raw -->
    <div class="section-title">iptables INPUT 현재 규칙</div>
    <div class="raw-rules"><?php
        exec("iptables -L INPUT -n --line-numbers -v 2>/dev/null", $raw);
        echo htmlspecialchars(implode("\n", $raw));
    ?></div>

</div>

<script>
function toggleWhite(port) {
    const el = document.getElementById('wf_' + port);
    el.classList.toggle('active');
}
</script>

</body>
</html>

<?php require_once G5_PATH.'/_PAGE/tail.php'; ?>