diff --git a/hamncheese/Scenes/settings.tscn b/hamncheese/Scenes/settings.tscn index 5e84320..cb9209c 100644 --- a/hamncheese/Scenes/settings.tscn +++ b/hamncheese/Scenes/settings.tscn @@ -53,6 +53,7 @@ unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 max_length = 32 +select_all_on_focus = true [node name="Label2" type="Label" parent="SettingsWindow/MarginContainer/VBoxContainer/NamesGridContainer"] layout_mode = 2 @@ -63,6 +64,7 @@ horizontal_alignment = 2 unique_name_in_owner = true layout_mode = 2 max_length = 32 +select_all_on_focus = true [node name="Label3" type="Label" parent="SettingsWindow/MarginContainer/VBoxContainer/NamesGridContainer"] layout_mode = 2 @@ -73,6 +75,7 @@ horizontal_alignment = 2 unique_name_in_owner = true layout_mode = 2 max_length = 32 +select_all_on_focus = true [node name="MarginContainer" type="MarginContainer" parent="SettingsWindow/MarginContainer/VBoxContainer"] custom_minimum_size = Vector2(0, 20) @@ -94,6 +97,7 @@ horizontal_alignment = 2 unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +select_all_on_focus = true [node name="Label2" type="Label" parent="SettingsWindow/MarginContainer/VBoxContainer/ServerGridContainer"] layout_mode = 2 @@ -106,6 +110,7 @@ layout_mode = 2 max_value = 65535.0 page = 100.0 rounded = true +select_all_on_focus = true [node name="MarginContainer2" type="MarginContainer" parent="SettingsWindow/MarginContainer/VBoxContainer"] custom_minimum_size = Vector2(0, 20) @@ -126,6 +131,7 @@ horizontal_alignment = 2 unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +select_all_on_focus = true [node name="IPCheckButton" type="CheckButton" parent="SettingsWindow/MarginContainer/VBoxContainer/AddressesGridContainer"] unique_name_in_owner = true @@ -141,6 +147,7 @@ horizontal_alignment = 2 unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +select_all_on_focus = true [node name="MACCheckButton" type="CheckButton" parent="SettingsWindow/MarginContainer/VBoxContainer/AddressesGridContainer"] unique_name_in_owner = true diff --git a/hamncheese/Scripts/dialog.gd b/hamncheese/Scripts/dialog.gd index 0712dfd..adc7279 100644 --- a/hamncheese/Scripts/dialog.gd +++ b/hamncheese/Scripts/dialog.gd @@ -5,7 +5,6 @@ signal _dialog_closed var _dialog -var _line_edit var _result @@ -64,27 +63,37 @@ func confirm(title: String, text: String, parent: Node = null): return _result -func prompt(title: String, text: String, parent: Node = null, chars: int = 32): +func password(title: String, text: String, dialogName: String = "", parent: Node = null, chars: int = 32): + # Do we have a password for this dialog? + if dialogName == "": + dialogName = title.to_lower().replace(" ", "_") + if Settings.passwords.has(dialogName): + return Settings.passwords[dialogName] + # No password, show dialog. var hbox := HBoxContainer.new() hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL var label := Label.new() label.text = text hbox.add_child(label) hbox.add_spacer(false) - _line_edit = LineEdit.new() - var size = _line_edit.get_minimum_size() - size.x = _line_edit.get_theme_default_font_size() * chars - _line_edit.set_custom_minimum_size(size) - _line_edit.max_length = chars - _line_edit.expand_to_text_length = true - _line_edit.secret = true - _line_edit.select_all_on_focus = true - _line_edit.text_submitted.connect(_submitted) - hbox.add_child(_line_edit) + var line_edit = LineEdit.new() + var size = line_edit.get_minimum_size() + size.x = line_edit.get_theme_default_font_size() * chars + line_edit.set_custom_minimum_size(size) + line_edit.max_length = chars + line_edit.expand_to_text_length = true + line_edit.secret = true + line_edit.select_all_on_focus = true + line_edit.text_submitted.connect(_submitted) + hbox.add_child(line_edit) + hbox.add_spacer(false) + var check_button = CheckButton.new() + check_button.text = "Save" + hbox.add_child(check_button) _dialog = AcceptDialog.new() _dialog.title = title _dialog.unresizable = true - _dialog.register_text_enter(_line_edit) + _dialog.register_text_enter(line_edit) var cancel = _dialog.add_cancel_button("Cancel") _dialog.get_ok_button().pressed.connect(_accepted) cancel.pressed.connect(_canceled) @@ -97,6 +106,48 @@ func prompt(title: String, text: String, parent: Node = null, chars: int = 32): _dialog.popup_centered() await _dialog_closed if _result: - return _line_edit.text + if check_button.button_pressed: + # Save password + Settings.passwords[dialogName] = line_edit.text + Settings.save_settings() + return line_edit.text + else: + return null + + +func prompt(title: String, text: String, parent: Node = null, chars: int = 32): + var hbox := HBoxContainer.new() + hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + var label := Label.new() + label.text = text + hbox.add_child(label) + hbox.add_spacer(false) + var line_edit = LineEdit.new() + var size = line_edit.get_minimum_size() + size.x = line_edit.get_theme_default_font_size() * chars + line_edit.set_custom_minimum_size(size) + line_edit.max_length = chars + line_edit.expand_to_text_length = true + line_edit.secret = false + line_edit.select_all_on_focus = true + line_edit.text_submitted.connect(_submitted) + hbox.add_child(line_edit) + _dialog = AcceptDialog.new() + _dialog.title = title + _dialog.unresizable = true + _dialog.register_text_enter(line_edit) + var cancel = _dialog.add_cancel_button("Cancel") + _dialog.get_ok_button().pressed.connect(_accepted) + cancel.pressed.connect(_canceled) + _dialog.add_child(hbox) + if parent == null: + var scene_tree = Engine.get_main_loop() + scene_tree.current_scene.add_child(_dialog) + else: + parent.add_child(_dialog) + _dialog.popup_centered() + await _dialog_closed + if _result: + return line_edit.text else: return null diff --git a/hamncheese/Scripts/main.gd b/hamncheese/Scripts/main.gd index 6fe5046..e07e7b5 100644 --- a/hamncheese/Scripts/main.gd +++ b/hamncheese/Scripts/main.gd @@ -1,5 +1,5 @@ # Things to do: -# - Add libzstd +# - Embed the "edge" binary and extract it if needed. # # Issues to watch: # - https://github.com/ntop/n2n/issues/1090 (P2P connections still occupy server traffic) @@ -51,20 +51,38 @@ func _data_recieved(type, data): Peers.start_server(_ip, PEER_BASE_PORT + 1, Settings.user_name_line_edit.text, Settings.uuid) if type == "edges": if Peers.update(data): - # Redraw peer tree. - peers_tree.clear() - #***TODO**** We should really be displaying things in columns. - var root = peers_tree.create_item() - for peer in Peers.peerArray: - print(peer["online"]) - if peer["online"]: - var child = peers_tree.create_item(root) - child.set_text(0, peer["user"] + " (" + peer["ip"] + ")") + _draw_tree() # Handle empty peer list. if data.size() == 0 and peers_tree.get_root() != null: peers_tree.clear() +func _draw_tree(): + # Redraw peer tree. + peers_tree.clear() + #***TODO**** We should really be displaying things in columns. + var root = peers_tree.create_item() + for peer in Peers.peerArray: + if peer["online"] or true: + var debug = str(peer["online"]) + " " + if peer["tcp"] == null: + debug = debug + "NULL" + else: + match peer["tcp"].get_status(): + StreamPeerTCP.STATUS_NONE: + debug = debug + "None" + StreamPeerTCP.STATUS_CONNECTING: + debug = debug + "Connecting" + StreamPeerTCP.STATUS_CONNECTED: + debug = debug + "Connected " + peer["tcp"].get_connected_host() + StreamPeerTCP.STATUS_ERROR: + debug = debug + "Error" + _: + debug = debug + "Unknown" + var child = peers_tree.create_item(root) + child.set_text(0, peer["user"] + " (" + peer["ip"] + ") " + debug) + + func _go_offline(): if Peers.is_running(): Peers.stop_server() diff --git a/hamncheese/Scripts/network.gd b/hamncheese/Scripts/network.gd index fb2b1af..4d5d236 100644 --- a/hamncheese/Scripts/network.gd +++ b/hamncheese/Scripts/network.gd @@ -11,14 +11,16 @@ func _ready(): func _received_packet(peer, verb, payload): match verb: - "online": - if peer["online"] != payload["status"]: - peer["online"] = payload["status"] - peer["refresh"] = true + "status": + for key in payload: + if peer[key] != payload[key]: + peer[key] = payload[key] + peer["refresh"] = true + if !peer["online"]: + Peers.disconnect_peer(peer) _: - print("UNKNOWN PACKET") + print("UNKNOWN VERB: ", verb) print(peer) - print(verb) print(payload) print() diff --git a/hamncheese/Scripts/peers.gd b/hamncheese/Scripts/peers.gd index 254a8f1..7eb74be 100644 --- a/hamncheese/Scripts/peers.gd +++ b/hamncheese/Scripts/peers.gd @@ -49,7 +49,7 @@ func _on_timer_timeout(): if peer["tcp"] != null: peer["tcp"].poll() if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: - send_to_peer(peer, "online", { "status": true }) + send_to_peer(peer, "status", { "online": true, "user": _userInfo["user"], "uuid": _userInfo["uuid"] }) func _process(_delta): @@ -92,39 +92,49 @@ func _tcp_server(): peer["tcp"].disconnect_from_host() peer["tcp"] = new_peer print("Updated peer ", peer["ip"], ":", peer["port"]) - peer["tcp"].set_no_delay(true) - - # Process incoming data and attempt connections. - for peer in peerArray: - if peer["tcp"] == null: - # Try to connect. - print("Connecting to ", peer["ip"], ":", peer["port"]) - peer["tcp"] = StreamPeerTCP.new() - var error = peer["tcp"].connect_to_host(peer["ip"], peer["port"]) - if error == OK: - peer["tcp"].set_no_delay(true) - else: - peer["tcp"] = null - else: - # Update network status. - peer["tcp"].poll() - # Still connected? - if peer["tcp"].get_status() == StreamPeerTCP.STATUS_ERROR: - peer["tcp"].disconnect_from_host() - peer["tcp"] = null - print("Lost peer ", peer["ip"], ":", peer["port"]) - else: - # Data waiting? + peer["tcp"].poll() if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: - if peer["tcp"].get_available_bytes() > 0: - var data = peer["tcp"].get_var() - peer_received_packet.emit(peer, data["verb"], data["payload"]) + peer["tcp"].set_no_delay(true) # ***TODO*** This throws an error some times. + + # Process incoming data and attempt connections. + for peer in peerArray: + if peer["tcp"] == null: + # Try to connect. + print("Connecting to ", peer["ip"], ":", peer["port"]) + peer["tcp"] = StreamPeerTCP.new() + var error = peer["tcp"].connect_to_host(peer["ip"], peer["port"]) + if error == OK: + peer["tcp"].set_no_delay(true) + else: + peer["tcp"] = null + else: + # Update network status. + peer["tcp"].poll() + # Still connected? + if peer["tcp"].get_status() == StreamPeerTCP.STATUS_ERROR: + peer["tcp"].disconnect_from_host() + peer["tcp"] = null + print("Lost peer ", peer["ip"], ":", peer["port"]) + else: + # Data waiting? + if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED: + if peer["tcp"].get_available_bytes() > 0: + var data = peer["tcp"].get_var() + peer_received_packet.emit(peer, data["verb"], data["payload"]) func clear(): peerArray.clear() +func disconnect_peer(peer): + if peer["tcp"] != null: + send_to_peer(peer, "status", { "online": false }) + peer["tcp"].disconnect_from_host() + peer["tcp"] = null + print("Disconnected peer ", peer["ip"], ":", peer["port"]) + + func is_running(): return _serverRunning @@ -158,11 +168,7 @@ func stop_server(): # Tear down network. _serverRunning = false for peer in peerArray: - if peer["tcp"] != null: - send_to_peer(peer, "online", { "status": false }) - peer["tcp"].disconnect_from_host() - peer["tcp"] = null - print("Disconnected peer ", peer["ip"], ":", peer["port"]) + disconnect_peer(peer) _tcp.stop() @@ -194,6 +200,10 @@ func update(peersFromCPP: Array): for oldPeer in oldArray: if oldPeer["ip"] == peerCPP["ip"]: found = true + # Sometimes we get duplicates, prevent adding a peer twice. + for knownPeer in peerArray: + if knownPeer["ip"] == oldPeer["ip"]: + continue peerCPP = oldPeer if !found: # We didn't have them. Add needed records. @@ -219,6 +229,6 @@ func update(peersFromCPP: Array): # Sort new array. peerArray.sort_custom(_sort_by_ip) - print("Peers After: ", peerArray) + #print("Peers After: ", peerArray) return changed diff --git a/hamncheese/Scripts/process.gd b/hamncheese/Scripts/process.gd index bac4e83..5b74a39 100644 --- a/hamncheese/Scripts/process.gd +++ b/hamncheese/Scripts/process.gd @@ -9,7 +9,7 @@ var _root_password = null func _elevate_unix(program: String, arguments: PackedStringArray): - _root_password = await Dialog.prompt("Root Access Needed", "Password:") + _root_password = await Dialog.password("Root Access Needed", "Password:") if _root_password == null: return -1 _ask_pass_password = Util.generate_password() @@ -26,7 +26,7 @@ func _elevate_unix(program: String, arguments: PackedStringArray): return pid -func _elevate_windows(program: String, arguments: PackedStringArray): +func _elevate_windows(_program: String, _arguments: PackedStringArray): pass diff --git a/hamncheese/Scripts/settings.gd b/hamncheese/Scripts/settings.gd index cb44ea8..a56ca8b 100644 --- a/hamncheese/Scripts/settings.gd +++ b/hamncheese/Scripts/settings.gd @@ -16,11 +16,16 @@ extends Control @onready var save_settings_button = %SaveSettingsButton +var _crypto +var _key + var uuid: String = "" +var passwords := {} func load_settings(): # Do we have a configuration yet? + passwords = {} var config = ConfigFile.new() var err = config.load("user://network.cfg") if err != OK: @@ -43,7 +48,6 @@ func load_settings(): # Load settings. user_name_line_edit.text = config.get_value("settings", "userName", "Change Me!") network_name_line_edit.text = config.get_value("settings", "networkName", "Change Me!") - network_password_line_edit.text = config.get_value("settings", "networkPassword", "Change Me!") server_line_edit.text = config.get_value("settings", "server", "supernode.kangaroopunch.com") server_port_spin_box.value = config.get_value("settings", "serverPort", 7777) ip_line_edit.text = config.get_value("settings", "ipAddress", "10.0.0.10") @@ -55,6 +59,13 @@ func load_settings(): compression_check_button.button_pressed = config.get_value("settings", "compression", true) auto_start_check_button.button_pressed = config.get_value("settings", "autoStart", false) uuid = config.get_value("settings", "identity", Uuid.v4()) + # Load passwords. + passwords["networkPassword"] = "" + if config.has_section("passwords"): + for key in config.get_section_keys("passwords"): + passwords[key] = config.get_value("passwords", key).hex_decode() + passwords[key] = _crypto.decrypt(_key, passwords[key]).get_string_from_utf8() + network_password_line_edit.text = passwords["networkPassword"] func save_settings(): @@ -76,9 +87,9 @@ func save_settings(): # Save it. var config = ConfigFile.new() + # Settings. config.set_value("settings", "userName", user_name_line_edit.text) config.set_value("settings", "networkName", network_name_line_edit.text) - config.set_value("settings", "networkPassword", network_password_line_edit.text) config.set_value("settings", "server", server_line_edit.text) config.set_value("settings", "serverPort", server_port_spin_box.value) config.set_value("settings", "ipAddress", ip_line_edit.text) @@ -89,9 +100,27 @@ func save_settings(): config.set_value("settings", "autoStart", auto_start_check_button.button_pressed) #config.set_value("settings", "position", DisplayServer.window_get_position()) config.set_value("settings", "identity", uuid) + # Passwords. + passwords["networkPassword"] = network_password_line_edit.text + for password in passwords: + var encrypted = _crypto.encrypt(_key, passwords[password].to_utf8_buffer()) + config.set_value("passwords", password, encrypted.hex_encode()) config.save("user://network.cfg") +func _init_crypto(): + _crypto = Crypto.new() + _key = CryptoKey.new() + if !FileAccess.file_exists("user://rsa.key") or !FileAccess.file_exists("user://rsa.crt"): + var cert = X509Certificate.new() + _key = _crypto.generate_rsa(4096) + cert = _crypto.generate_self_signed_certificate(_key, "CN=kangaroopunch.com,O=Kangaroo Punch Studios,C=US") + _key.save("user://rsa.key") + cert.save("user://rsa.crt") + else: + _key.load("user://rsa.key") + + func _on_ip_check_button_toggled(_toggled_on): ip_line_edit.editable = !ip_check_button.button_pressed @@ -110,5 +139,6 @@ func _on_settings_window_close_requested(): func _ready(): + _init_crypto() load_settings()