Sistem Asistanı (v1.1) / sayfalar/port_yoneticisi.py
port_yoneticisi.py 165 satır • 6.59 KB
# sayfalar/port_yoneticisi.py

from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, 
                             QPushButton, QHeaderView, QMessageBox, QGroupBox, QLabel, QLineEdit, QComboBox)
from PyQt6.QtGui import QColor
from gorsel_araclar import SayfaBasligi, SvgIkonOlusturucu
import subprocess
import psutil
import shutil
import os

class PortYoneticisiSayfasi(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(self)
        icon = SvgIkonOlusturucu.port_ikonu("#33AADD", 32)
        layout.addWidget(SayfaBasligi("Port Yöneticisi", icon))

        layout.addWidget(QLabel("Aktif Bağlantılar ve Dinlenen Portlar:"))
        
        self.table = QTableWidget()
        self.table.setColumnCount(5)
        self.table.setHorizontalHeaderLabels(["PID", "Uygulama", "Protokol", "Yerel Adres", "Durum"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        # Stil temizlendi
        layout.addWidget(self.table)

        h_btn_list = QHBoxLayout()
        btn_refresh = QPushButton("🔄 Listeyi Yenile")
        btn_refresh.clicked.connect(self.yenile)
        h_btn_list.addWidget(btn_refresh)
        
        btn_kill = QPushButton("⛔ Seçili İşlemi (PID) Sonlandır")
        btn_kill.setStyleSheet("background-color: #c0392b; color: white; font-weight: bold;")
        btn_kill.clicked.connect(self.oldur)
        h_btn_list.addWidget(btn_kill)
        layout.addLayout(h_btn_list)

        layout.addSpacing(20)

        # --- ALT KISIM: PORT AÇMA / KAPAMA ---
        grp_firewall = QGroupBox("Güvenlik Duvarı (UFW) Port Yönetimi")
        l_fw = QHBoxLayout(grp_firewall)

        l_fw.addWidget(QLabel("Port No:"))
        self.txt_port = QLineEdit()
        self.txt_port.setPlaceholderText("Örn: 8080")
        self.txt_port.setFixedWidth(100)
        l_fw.addWidget(self.txt_port)

        l_fw.addWidget(QLabel("Protokol:"))
        self.combo_proto = QComboBox()
        self.combo_proto.addItems(["tcp", "udp"])
        l_fw.addWidget(self.combo_proto)

        # Butonlar
        btn_allow = QPushButton("✅ Portu Aç (Allow)")
        btn_allow.setStyleSheet("background-color: #27ae60; color: white; font-weight: bold;")
        btn_allow.clicked.connect(lambda: self.port_islem("allow"))
        l_fw.addWidget(btn_allow)

        btn_deny = QPushButton("⛔ Portu Kapat (Deny)")
        btn_deny.setStyleSheet("background-color: #c0392b; color: white; font-weight: bold;")
        btn_deny.clicked.connect(lambda: self.port_islem("deny"))
        l_fw.addWidget(btn_deny)
        
        btn_delete = QPushButton("🗑️ Kuralı Sil")
        btn_delete.setStyleSheet("background-color: #7f8c8d; color: white;")
        btn_delete.clicked.connect(lambda: self.port_islem("delete"))
        l_fw.addWidget(btn_delete)

        layout.addWidget(grp_firewall)

        self.yenile()

    def yenile(self):
        self.table.setRowCount(0)
        try:
            conns = psutil.net_connections(kind='inet')
            for c in conns:
                laddr = f"{c.laddr.ip}:{c.laddr.port}"
                proto = "TCP" if c.type == 1 else "UDP"
                status = c.status
                pid = c.pid
                name = ""
                
                if pid:
                    try: name = psutil.Process(pid).name()
                    except: name = "Bilinmiyor"
                
                row = self.table.rowCount()
                self.table.insertRow(row)
                self.table.setItem(row, 0, QTableWidgetItem(str(pid) if pid else "-"))
                self.table.setItem(row, 1, QTableWidgetItem(name))
                self.table.setItem(row, 2, QTableWidgetItem(proto))
                self.table.setItem(row, 3, QTableWidgetItem(laddr))
                self.table.setItem(row, 4, QTableWidgetItem(status))
                
                if status == "LISTEN":
                    self.table.item(row, 4).setForeground(QColor("#2ecc71"))

        except Exception as e:
            pass

    def oldur(self):
        row = self.table.currentRow()
        if row < 0: 
            QMessageBox.warning(self, "Seçim Yok", "Lütfen listeden bir işlem seçin.")
            return
        
        pid_item = self.table.item(row, 0)
        name_item = self.table.item(row, 1)
        
        if not pid_item or pid_item.text() == "-": return
        
        pid = pid_item.text()
        name = name_item.text()
        
        if QMessageBox.question(self, "Onay", f"'{name}' (PID: {pid}) uygulamasını sonlandırmak istiyor musunuz?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) == QMessageBox.StandardButton.Yes:
            try:
                subprocess.run(["pkexec", "kill", "-9", pid], check=True)
                self.yenile()
                QMessageBox.information(self, "Başarılı", "İşlem sonlandırıldı.")
            except:
                QMessageBox.critical(self, "Hata", "İşlem sonlandırılamadı (Yetki reddedildi).")

    def port_islem(self, islem):
        port = self.txt_port.text().strip()
        proto = self.combo_proto.currentText()
        
        if not port.isdigit():
            QMessageBox.warning(self, "Hata", "Lütfen geçerli bir port numarası girin.")
            return
        
        ufw_path = shutil.which("ufw")
        if not ufw_path and os.path.exists("/usr/sbin/ufw"): 
            ufw_path = "/usr/sbin/ufw"
        
        if not ufw_path:
            QMessageBox.critical(self, "Hata", "UFW (Güvenlik Duvarı) sistemde bulunamadı.")
            return

        cmd = ["pkexec", ufw_path]
        
        if islem == "delete":
            cmd_str = (
                f"{ufw_path} --force delete allow {port}/{proto}; "
                f"{ufw_path} --force delete deny {port}/{proto}"
            )
            try:
                subprocess.run(["pkexec", "sh", "-c", cmd_str], check=False)
                QMessageBox.information(self, "Bilgi", f"{port}/{proto} için tanımlı kurallar temizlendi.")
            except Exception as e:
                QMessageBox.critical(self, "Hata", f"Silme işlemi başarısız: {e}")
            return

        cmd.extend([islem, f"{port}/{proto}"])
        
        try:
            subprocess.run(cmd, check=True)
            QMessageBox.information(self, "Başarılı", f"{port}/{proto} kuralı uygulandı ({islem}).")
        except subprocess.CalledProcessError:
            QMessageBox.warning(self, "Hata", "İşlem yapılamadı (Yetki reddedildi veya hata oluştu).")
        except Exception as e:
            QMessageBox.critical(self, "Hata", f"Beklenmeyen hata: {str(e)}")