diff --git a/debian/control b/debian/control index 4e56185..f56e60f 100644 --- a/debian/control +++ b/debian/control @@ -15,7 +15,8 @@ Depends: ${misc:Depends}, gir1.2-glib-2.0, gir1.2-gtk-3.0, gir1.2-notify-0.7, - gir1.2-pango-1.0 + gir1.2-pango-1.0, + python3-paramiko Recommends: gvfs-fuse, libglib2.0-bin Description: My Computer, shows general information about your computer. diff --git a/po/files b/po/files index 414622e..0f6c8e9 100644 --- a/po/files +++ b/po/files @@ -1,3 +1,4 @@ ui/MainWindow.glade src/MainWindow.py src/UserSettings.py +src/Scansftp.py \ No newline at end of file diff --git a/po/tr.po b/po/tr.po index 2a259ab..c64051c 100644 --- a/po/tr.po +++ b/po/tr.po @@ -512,3 +512,15 @@ msgstr "# not: görünmesini sağlamak için kare işareti kaldırın" #~ msgid "Website" #~ msgstr "Website" + +#: ui/MainWindow.glade:2248 +msgid "Find the servers connected to the network:" +msgstr "Ağa bağlı sunucuları bulun:" + +#: ui/MainWindow.glade:2248 +msgid "Scanning the SFTP servers on the network..." +msgstr "Ağdaki SFTP sunucuları taranıyor..." + +#: ui/MainWindow.glade:2344 +msgid "Scan" +msgstr "Tara" diff --git a/setup.py b/setup.py index ea70c31..77c8ec7 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ def create_mo_files(): "src/DiskManager.py", "src/Unmount.py", "src/UserSettings.py", + "src/Scansftp.py", "src/__version__" ]), ("/usr/share/pardus/pardus-mycomputer/ui", diff --git a/src/MainWindow.py b/src/MainWindow.py index efb9a3b..3afb520 100644 --- a/src/MainWindow.py +++ b/src/MainWindow.py @@ -14,6 +14,9 @@ from UserSettings import UserSettings +import threading +import Scansftp # sftp scan function library + import locale from locale import gettext as _ @@ -213,6 +216,10 @@ def UI(str): self.box_user_domain_pass = UI("box_user_domain_pass") self.box_anonym = UI("box_anonym") self.btn_mount_connect = UI("btn_mount_connect") + self.btn_sftp_scan = UI("btn_sftp_scan") # sftp scan button + self.sftp_listbox = UI("sftp_listbox") # sftp list box + self.sftp_stack = UI("sftp_stack") # sftp stack panel + self.scrolledwindow = UI("scrolledwindow") self.mount_password_options = UI("mount_password_options") self.mount_anonym_options = UI("mount_anonym_options") self.popover_connect = UI("popover_connect") @@ -2103,6 +2110,55 @@ def remove_from_recent_clicked(self, button): self.UserSettings.removeRecentServer(button.name) + + + # sftp scan function + def on_sftp_scan(self, button): + self.sftp_stack.set_visible_child_name('page1') + self.btn_sftp_scan.set_sensitive(False) + # Start background thread + threading.Thread(target=self.scan_process, daemon=True).start() + + def scan_process(self): + try: + netaddress = Scansftp.get_local_network() + scanned_devices = Scansftp.scan_devices(netaddress) + except Exception as e: + return + GLib.idle_add(self.sftp_listboxadd, scanned_devices) + + def sftp_listboxadd(self, scanned_devices): + # Update the list + self.sftp_listbox.foreach(lambda w: self.sftp_listbox.remove(w)) + self.sftp_listbox.set_hexpand(True) + self.sftp_listbox.set_vexpand(True) + for d in scanned_devices: + row = self.make_sftp_listbox_button(d, on_clicked=self.server_entry_settext) + self.sftp_listbox.add(row) + + self.sftp_stack.set_visible_child_name('page0') + self.sftp_listbox.show_all() + self.btn_sftp_scan.set_sensitive(True) + + def make_sftp_listbox_button(self, label_text, on_clicked=None): + # Create row + row = Gtk.ListBoxRow() + + # Create a button. You can edit it using align/halign + btn = Gtk.Button(label=label_text) + btn.set_hexpand(True) + btn.set_halign(Gtk.Align.FILL) + + if on_clicked is not None: + btn.connect("clicked", on_clicked) + row.add(btn) + return row + + def server_entry_settext(self, button): + ip = button.get_label().strip() + self.entry_addr.set_text("sftp://"+ip) + + def on_btn_mount_connect_clicked(self, button, from_saved=False, saved_uri="", from_places=False): def get_uri_name(source_object): try: diff --git a/src/Scansftp.py b/src/Scansftp.py new file mode 100644 index 0000000..187dd17 --- /dev/null +++ b/src/Scansftp.py @@ -0,0 +1,102 @@ +import subprocess +import socket +import ipaddress +from concurrent.futures import ThreadPoolExecutor, as_completed +import time +import paramiko +import netifaces + +# a function that retrieves the network address of the connected interface +def get_local_network(): + # Find the interface used by the default gateway + gateway_info = netifaces.gateways() + interface = gateway_info['default'][netifaces.AF_INET][1] + + # Interface IP and netmask information + addr_info = netifaces.ifaddresses(interface)[netifaces.AF_INET][0] + + ip_addr = addr_info['addr'] + netmask = addr_info['netmask'] + + # Create a network address + network = ipaddress.IPv4Network(f"{ip_addr}/{netmask}", strict=False) + + return str(network) + + +def get_ip_neigh(): + ips = set() + try: + out = subprocess.check_output(["ip", "neigh"]).decode() + for line in out.splitlines(): + parts = line.split() + if len(parts) > 0: + ip = parts[0] + if ip.count(".") == 3: # IPv4 basic filter + ips.add(ip) + except Exception as e: + print("ip neigh error:", e) + + return ips + + +def check_ssh_sftp(host, port=22, timeout=3): + # Checking if the SSH port is open + try: + sock = socket.create_connection((host, port), timeout=timeout) + sock.close() + except (socket.timeout, ConnectionRefusedError, OSError): + return False + + # Sending a fake credential results in an AuthenticationException → This means SFTP exists + transport = paramiko.Transport((host, port)) + transport.banner_timeout = timeout + + try: + transport.connect( + username='__probe__', + password='__probe__' + ) + return True + + except paramiko.AuthenticationException: + return True # an authentication error means the SFTP service is active + + except paramiko.SSHException: + return False # SSH handshake failed + + except Exception: + return False + +# scan devices +def scan_devices(network, max_threads=30, delay=0.02): + net = ipaddress.ip_network(network, strict=False) + results = [] + + def worker(ip): + ip = str(ip) + if check_ssh_sftp(ip): # this address is being checked to see if it is an SSH server + return ip + return None + + with ThreadPoolExecutor(max_workers=max_threads) as executor: + futures = [] + + for ip in net.hosts(): + futures.append(executor.submit(worker, ip)) + time.sleep(delay) + + for f in as_completed(futures): + res = f.result() + if res: + results.append(res) + + print(results) + return results + + +#netaddress = get_local_network() +#hosts = scan(netaddress) +#for h in hosts: +# print(h) + diff --git a/ui/MainWindow.glade b/ui/MainWindow.glade index 249ce41..abf549b 100644 --- a/ui/MainWindow.glade +++ b/ui/MainWindow.glade @@ -2223,6 +2223,144 @@ Fatih Altun <fatih.altun@pardus.org.tr> 1 + + + True + False + + + False + True + 2 + + + + + True + False + 10 + vertical + + + True + False + 13 + Find the servers connected to the network: + + + False + True + 0 + + + + + True + False + + + True + False + vertical + + + True + True + 3 + in + True + True + + + True + False + + + True + False + + + + + + + False + True + 0 + + + + + page0 + page0 + + + + + True + False + vertical + True + + + True + False + True + + + False + True + 0 + + + + + True + False + Scanning the SFTP servers on the network... + + + False + True + 1 + + + + + page1 + page1 + 1 + + + + + False + True + 1 + + + + + Scan + True + True + True + 16 + + + + False + True + 2 + + + + + False + True + 3 + +