diff --git a/.gitignore b/.gitignore index 2126236..bf98554 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ server/target/ Godot_* .current_version +.cert/ diff --git a/README.md b/README.md index 1925d5d..4e350b1 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ When you're ready to make this README your own, just edit this file and use the Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. ## Name -Choose a self-explaining name for your project. +Project just to test network functionnality (validated communication between client & server, validated xmpp) +And test client 3d ## Description Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. @@ -64,10 +65,20 @@ On some READMEs, you may see small images that convey metadata, such as whether Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. ## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. + +### Create certicifate +./create-certificate.sh ## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + +### Launch server +./start-bazar-server.sh + +### Launch XMPP server +./start-bazar-fake-xmpp-server.sh + +### Launch client +./start-bazar-client.sh ## Support Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. @@ -83,10 +94,10 @@ For people who want to make changes to your project, it's helpful to have some d You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. ## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +AleaJactaEst ## License -For open source projects, say how it is licensed. + GNU GPL v3 ## Project status If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/client/assets/preference.png b/client/assets/preference.png new file mode 100644 index 0000000..fce1da7 Binary files /dev/null and b/client/assets/preference.png differ diff --git a/client/scenes/PreferenceServer.gd b/client/scenes/PreferenceServer.gd new file mode 100644 index 0000000..9df619d --- /dev/null +++ b/client/scenes/PreferenceServer.gd @@ -0,0 +1,56 @@ +extends Window + + +signal send_server_config(alea_ip, alea_port, xmpp_ip, xmpp_port) + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func show_window(): + self._on_text_changed("") + self.show() + +func _on_button_pressed(): + send_server_config.emit( + $VBoxContainer/TabContainer/Alea/Address.get_text(), + int($VBoxContainer/TabContainer/Alea/Port.get_text()), + $VBoxContainer/TabContainer/XMPP/Address.get_text(), + int($VBoxContainer/TabContainer/XMPP/Port.get_text()) + ) + self.hide() + + +func _on_text_changed(_text): + var listen_port:int + if len($VBoxContainer/TabContainer/Alea/Address.get_text()) == 0: + $VBoxContainer/Button.set_disabled(true) + return + if len($VBoxContainer/TabContainer/Alea/Port.get_text()) == 0: + $VBoxContainer/Button.set_disabled(true) + return + if len($VBoxContainer/TabContainer/XMPP/Address.get_text()) == 0: + $VBoxContainer/Button.set_disabled(true) + return + if len($VBoxContainer/TabContainer/XMPP/Port.get_text()) == 0: + $VBoxContainer/Button.set_disabled(true) + return + listen_port = int($VBoxContainer/TabContainer/Alea/Port.get_text()) + if listen_port <= 0 || listen_port >= 65536 : + $VBoxContainer/Button.set_disabled(true) + return + listen_port = int($VBoxContainer/TabContainer/XMPP/Port.get_text()) + if listen_port <= 0 || listen_port >= 65536 : + $VBoxContainer/Button.set_disabled(true) + return + $VBoxContainer/Button.set_disabled(false) + + +func _on_close_requested(): + self.hide() diff --git a/client/scenes/Window.gd b/client/scenes/Window.gd index d52d7a7..da64907 100644 --- a/client/scenes/Window.gd +++ b/client/scenes/Window.gd @@ -3,8 +3,10 @@ extends Window var focus_ok:bool = false var last_event = null -@export var listen_ip:String = "" -@export var listen_port:int = 33333 +signal show_preference_server() + +#@export var listen_ip:String = "" +#@export var listen_port:int = 33333 # Called when the node enters the scene tree for the first time. func _ready(): @@ -17,33 +19,28 @@ func _process(_delta): func _on_button_pressed(): - if $VBoxContainer/TextEdit.get_text() != "": + if $VBoxContainer/Account.get_text() != "": self.hide() #get_parent().get_node("CharacterBody3D").set_enable_event(true) #get_parent().connect_enet($VBoxContainer/TextEdit.get_text(), listen_ip, listen_port) - Multi.set_username($VBoxContainer/TextEdit.get_text()) + Multi.set_username($VBoxContainer/Account.get_text()) #Multi.connect_server() func check_testedit(): - if len($VBoxContainer/TextEdit.get_text()) == 0: + if len($VBoxContainer/Account.get_text()) == 0: $VBoxContainer/Button.set_disabled(true) return - if len($VBoxContainer/Address.get_text()) == 0: - $VBoxContainer/Button.set_disabled(true) - return - listen_ip = $VBoxContainer/Address.get_text() - listen_port = int($VBoxContainer/Port.get_text()) - if listen_port <= 0 || listen_port >= 65536 : + if len($VBoxContainer/Password.get_text()) == 0: $VBoxContainer/Button.set_disabled(true) return $VBoxContainer/Button.set_disabled(false) - Multi.set_ip(listen_ip) - Multi.set_port(listen_port) func _on_text_edit_text_changed(_value:String): check_testedit() +func _on_password_text_changed(_value:String): + check_testedit() func _on_focus_entered(): focus_ok = true @@ -74,3 +71,8 @@ func open_windows(): # return # if (event is InputEventKey) && (self.type_event == 1): # last_event = event + + +func _on_preference_pressed(): + show_preference_server.emit() + diff --git a/client/scenes/main.gd b/client/scenes/main.gd index 6317471..d4fea53 100644 --- a/client/scenes/main.gd +++ b/client/scenes/main.gd @@ -22,27 +22,27 @@ func _ready(): Multi.update_my_position.connect(_on_update_me) Multi.update_player_position.connect(_on_update_player) Multi.remove_player.connect(_on_remove_player) - Stream.set_server_ip("127.0.0.1") - Stream.server_ip = "127.0.0.1" - print(Stream.get_server_ip()) + $Window.show_preference_server.connect(_on_preference_server) + $PreferenceServer.send_server_config.connect(_on_edit_preference_server) + $PreferenceServer._on_button_pressed() func _on_connexion_updated(new_state): if new_state == Multi.Connexion.NONE: self.get_node("CharacterBody3D").set_enable_event(false) - $Panel/State.set_text("Not Connected") - $Panel.show() + $StateAlea/VBoxContainer/State.set_text("Alea: Not Connected") + $StateAlea.show() if new_state == Multi.Connexion.ACCOUNT_REFUSED: self.get_node("CharacterBody3D").set_enable_event(false) - $Panel/State.set_text("Account Refused") - $Panel.show() + $StateAlea/VBoxContainer/State.set_text("Alea: Account Refused") + $StateAlea.show() $Window.show() elif new_state == Multi.Connexion.CONNECTING: self.get_node("CharacterBody3D").set_enable_event(false) - $Panel/State.set_text("Connecting") - $Panel.show() + $StateAlea/VBoxContainer/State.set_text("Alea: Connecting") + $StateAlea.show() else: - $Panel.hide() + $StateAlea.hide() func _on_update_me(pos:Vector3): @@ -93,3 +93,14 @@ func _on_remove_player(id:int): func set_player_position(pos: Vector3): self.get_node("CharacterBody3D").set_global_position(pos) + + +func _on_preference_server(): + $PreferenceServer.show_window() + +func _on_edit_preference_server(alea_ip, alea_port, xmpp_ip, xmpp_port): + print(alea_ip, ', ', alea_port, ', ', xmpp_ip, ', ', xmpp_port) + Multi.set_ip(alea_ip) + Multi.set_port(alea_port) + Stream.set_server_ip(xmpp_ip) + Stream.set_port_number(xmpp_port) diff --git a/client/scenes/main.tscn b/client/scenes/main.tscn index 737ddaf..2ae6efa 100644 --- a/client/scenes/main.tscn +++ b/client/scenes/main.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=11 format=3 uid="uid://bemavktwweaog"] +[gd_scene load_steps=13 format=3 uid="uid://bemavktwweaog"] [ext_resource type="Shader" path="res://scenes/main.gdshader" id="1_caff6"] [ext_resource type="PackedScene" uid="uid://cg5uqqd4ibdem" path="res://scenes/player.tscn" id="1_nc7b3"] [ext_resource type="Script" path="res://scenes/main.gd" id="1_ts8of"] [ext_resource type="Script" path="res://scenes/Window.gd" id="3_uwnj8"] +[ext_resource type="Texture2D" uid="uid://cc7b2q50tp3yn" path="res://assets/preference.png" id="5_c5esr"] +[ext_resource type="Script" path="res://scenes/PreferenceServer.gd" id="6_54nnr"] [sub_resource type="BoxShape3D" id="BoxShape3D_5dcgs"] size = Vector3(1000, 1, 1000) @@ -66,49 +68,136 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="Label" type="Label" parent="Window/VBoxContainer"] +[node name="HBoxContainer" type="HBoxContainer" parent="Window/VBoxContainer"] layout_mode = 2 + +[node name="Label" type="Label" parent="Window/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 text = "Login" horizontal_alignment = 1 +vertical_alignment = 1 -[node name="Address" type="LineEdit" parent="Window/VBoxContainer"] +[node name="Preference" type="Button" parent="Window/VBoxContainer/HBoxContainer"] layout_mode = 2 -size_flags_vertical = 3 -text = "127.0.0.1" -placeholder_text = "Address" +size_flags_horizontal = 8 +icon = ExtResource("5_c5esr") +flat = true -[node name="Port" type="LineEdit" parent="Window/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 -text = "33333" -placeholder_text = "Address" - -[node name="TextEdit" type="LineEdit" parent="Window/VBoxContainer"] +[node name="Account" type="LineEdit" parent="Window/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 placeholder_text = "Account" +[node name="Password" type="LineEdit" parent="Window/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Password" + [node name="Button" type="Button" parent="Window/VBoxContainer"] layout_mode = 2 disabled = true text = "OK" -[node name="Panel" type="Panel" parent="."] +[node name="StateAlea" type="Panel" parent="."] offset_right = 199.0 -offset_bottom = 40.0 +offset_bottom = 36.0 size_flags_horizontal = 3 size_flags_vertical = 3 -[node name="State" type="Label" parent="Panel"] +[node name="VBoxContainer" type="VBoxContainer" parent="StateAlea"] layout_mode = 1 -anchors_preset = -1 -anchor_right = 0.994 -anchor_bottom = 0.75 -offset_right = -0.0279999 -offset_bottom = 10.0 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -text = "Not connected" + +[node name="State" type="Label" parent="StateAlea/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +text = "ALEA: Not connected" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="PreferenceServer" type="Window" parent="."] +title = "Preference Server" +initial_position = 2 +size = Vector2i(300, 300) +visible = false +exclusive = true +always_on_top = true +script = ExtResource("6_54nnr") + +[node name="VBoxContainer" type="VBoxContainer" parent="PreferenceServer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TabContainer" type="TabContainer" parent="PreferenceServer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Alea" type="VBoxContainer" parent="PreferenceServer/VBoxContainer/TabContainer"] +layout_mode = 2 + +[node name="Address" type="LineEdit" parent="PreferenceServer/VBoxContainer/TabContainer/Alea"] +layout_mode = 2 +size_flags_vertical = 3 +text = "127.0.0.1" +placeholder_text = "Address" + +[node name="Port" type="LineEdit" parent="PreferenceServer/VBoxContainer/TabContainer/Alea"] +layout_mode = 2 +size_flags_vertical = 3 +text = "33333" +placeholder_text = "Address" + +[node name="XMPP" type="VBoxContainer" parent="PreferenceServer/VBoxContainer/TabContainer"] +visible = false +layout_mode = 2 + +[node name="Address" type="LineEdit" parent="PreferenceServer/VBoxContainer/TabContainer/XMPP"] +layout_mode = 2 +size_flags_vertical = 3 +text = "127.0.0.1" +placeholder_text = "Address" + +[node name="Port" type="LineEdit" parent="PreferenceServer/VBoxContainer/TabContainer/XMPP"] +layout_mode = 2 +size_flags_vertical = 3 +text = "5222" +placeholder_text = "Address" + +[node name="Button" type="Button" parent="PreferenceServer/VBoxContainer"] +layout_mode = 2 +disabled = true +text = "Done" + +[node name="StateXmpp" type="Panel" parent="."] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -199.0 +offset_bottom = 36.0 +grow_horizontal = 0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="StateXmpp"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="State" type="Label" parent="StateXmpp/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +text = "XMPP: Not connected" horizontal_alignment = 1 vertical_alignment = 1 @@ -116,7 +205,13 @@ vertical_alignment = 1 [connection signal="focus_exited" from="Window" to="Window" method="_on_focus_exited"] [connection signal="mouse_entered" from="Window" to="Window" method="_on_mouse_entered"] [connection signal="mouse_exited" from="Window" to="Window" method="_on_mouse_exited"] -[connection signal="text_changed" from="Window/VBoxContainer/Address" to="Window" method="_on_text_edit_text_changed"] -[connection signal="text_changed" from="Window/VBoxContainer/Port" to="Window" method="_on_text_edit_text_changed"] -[connection signal="text_changed" from="Window/VBoxContainer/TextEdit" to="Window" method="_on_text_edit_text_changed"] +[connection signal="pressed" from="Window/VBoxContainer/HBoxContainer/Preference" to="Window" method="_on_preference_pressed"] +[connection signal="text_changed" from="Window/VBoxContainer/Account" to="Window" method="_on_text_edit_text_changed"] +[connection signal="text_changed" from="Window/VBoxContainer/Password" to="Window" method="_on_password_text_changed"] [connection signal="pressed" from="Window/VBoxContainer/Button" to="Window" method="_on_button_pressed"] +[connection signal="close_requested" from="PreferenceServer" to="PreferenceServer" method="_on_close_requested"] +[connection signal="text_changed" from="PreferenceServer/VBoxContainer/TabContainer/Alea/Address" to="PreferenceServer" method="_on_text_changed"] +[connection signal="text_changed" from="PreferenceServer/VBoxContainer/TabContainer/Alea/Port" to="PreferenceServer" method="_on_text_changed"] +[connection signal="text_changed" from="PreferenceServer/VBoxContainer/TabContainer/XMPP/Address" to="PreferenceServer" method="_on_text_changed"] +[connection signal="text_changed" from="PreferenceServer/VBoxContainer/TabContainer/XMPP/Port" to="PreferenceServer" method="_on_text_changed"] +[connection signal="pressed" from="PreferenceServer/VBoxContainer/Button" to="PreferenceServer" method="_on_button_pressed"] diff --git a/client/xmpp/stream.gd b/client/xmpp/stream.gd index ebfd22f..a163f70 100644 --- a/client/xmpp/stream.gd +++ b/client/xmpp/stream.gd @@ -73,6 +73,7 @@ func _init(): set_locale(language) func _process(delta): + print("TCP:", tcp_peer.get_status()) if (tcp_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED) : if (tcp_peer.has_method("poll")): tcp_peer.poll() @@ -92,6 +93,48 @@ func _process(delta): set_process(false) # stop listening for packets +### +# Connect to the server ip and port, and start checking whether there's stream +# info yet. +### +func connect_to_server(server:String, port:int): + if tcp_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: + pass + tcp_peer.connect_to_host(server, port) + count_connecting_time = 0 + set_process(true) + + +### +# Send a string in the appropriate encoding. +### +func send_string(stanza:String): + debug.emit("Sending data: [color=blue]%[/color]".format([stanza], "%")) + tcp_peer.put_data(stanza.to_utf8_buffer()) + + +### +# End the stream. +### +func end_stream(): + debug.emit("Stream: Ending stream") + send_string("") + if tcp_peer.has_method("disconnect_from_stream"): + tcp_peer.disconnect_from_stream() + else: + tcp_peer.disconnect_from_host() + set_process(false) + stream_status = StreamState.END + + +func connect_stream(): + if (tcp_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED): + end_stream() + tcp_peer = StreamPeerTCP.new() + stream_status = StreamState.START + stream_process() + + func remove_stream_header(text :String) -> String: var index = 0 if text.begins_with("" + send_string(request_tls) + stream_status = self.StreamState.TLS + + elif stream_features.parsedDictionary.has("mechanisms"): + var authentication_methods = stream_features.parsedDictionary["mechanisms"] + if (!authentication_methods.empty()): + debug.emit("Stream: authentication methods: " + authentication_methods.join(", ")) + negotiate_sasl(authentication_methods) + stream_status = self.StreamState.AUTHENTICATE + + else: + if stream_status == StreamState.TLS: + if response.begins_with("") + streamstart = streamstart.join(" ") + send_string(str(xmlVersion + streamstart)) + + +### +# Try to switch to an SSL based connection. +### +func negotiate_sasl(authentication_methods : Array): + if (!authentication_methods.has("PLAIN")): + end_stream() + debug.emit("Stream: sending request for plain") + var auth_account = "\u0000"+account_name.split("@")[0] + auth_account += "\u0000" + auth_account += password + auth_account = Marshalls.utf8_to_base64(auth_account) + var request_sasl = [] + request_sasl.append("%".format([auth_account], "%")) + send_string(request_sasl.join(" ")) diff --git a/client/xmpp/xmpp_stream_errors.gd b/client/xmpp/xmpp_stream_errors.gd new file mode 100644 index 0000000..ab18fa8 --- /dev/null +++ b/client/xmpp/xmpp_stream_errors.gd @@ -0,0 +1,149 @@ +### +# This file is part of Godot XMPP Client +# SPDX-FileCopyrightText: 2020 Wolthera van Hövell tot Westerflier +# +# SPDX-License-Identifier: MIT +### + +extends Resource + +class_name XMPPStreamError + +### +# This is a parser for stream errors, that keeps a list of human friendly error-explainations. +# Stream errors, unlike stanza errors, will always close the stream. +### + +enum StreamErrorTypes{ + bad_format, + bad_namespace_prefix, + conflict, + connection_timeout, + host_gone, + host_unknown, + improper_addressing, + internal_server_error, + invalid_from, + invalid_namespace, + invalid_xml, + not_authorized, + not_well_formed, + policy_violation, + remote_connection_failed, + reset, + resource_constraint, + restricted_xml, + see_other_host, + system_shutdown, + undefined_condition, + unsupported_encoding, + unsupported_feature, + unsupported_stanza_type, + unsupported_version +} + +var humane_error_messages = { + StreamErrorTypes.bad_format: + tr("The sent message cannot be processed."), + StreamErrorTypes.bad_namespace_prefix: + tr("The namespace cannot be recognized"), + StreamErrorTypes.conflict: + tr("This connection conflicts with another connection coming from the same address, which makes it impossible to differentiate between the connections."), + StreamErrorTypes.connection_timeout: + tr("This client took too long to respond, and thus the server assumed the device had lost internet connection."), + StreamErrorTypes.host_gone: + tr("The address of the server has changed."), + StreamErrorTypes.host_unknown: + tr("The server does not know the address."), + StreamErrorTypes.improper_addressing: + tr("The address is missing from the sent message."), + StreamErrorTypes.internal_server_error: + tr("The server is experiencing issues, try again later."), + StreamErrorTypes.invalid_from: + tr("This connection is not allowed to sign it's messages with the given address."), + StreamErrorTypes.invalid_namespace: + tr("The message was formatted with an incorrect namespace."), + StreamErrorTypes.invalid_xml: + tr("The message was formatted with invalid xml."), + StreamErrorTypes.not_authorized: + tr("The client is not allowed to sent these messages because it is not authorized to do so."), + StreamErrorTypes.not_well_formed: + tr("The message was formatted with incorrectly formed xml."), + StreamErrorTypes.policy_violation: + tr("The server determined the message violated server policy in some technical manner."), + StreamErrorTypes.remote_connection_failed: + tr("The server is unable to deliver the message because it cannot connect to the other server."), + StreamErrorTypes.reset: + tr("The server closed the connection because it deemed to connection needed to be reset for either feature or security purposes."), + StreamErrorTypes.resource_constraint: + tr("The server is too busy to handle this stream."), + StreamErrorTypes.restricted_xml: + tr("The message contained restricted xml (such as comments, processing instructions, dtd subset, or an xml entity reference)"), + StreamErrorTypes.see_other_host: + tr("The server is redirecting the client to a different host."), + StreamErrorTypes.system_shutdown: + tr("The server is being shut down."), + StreamErrorTypes.undefined_condition: + tr("A special case error has occured. See included xml stream."), + StreamErrorTypes.unsupported_encoding: + tr("The message was encoded with an encoding other than UTF-8, or contained extra bytes that the server is not expecting (as possible with godot's tls functions)."), + StreamErrorTypes.unsupported_feature: + tr("The required stream features are not supported."), + StreamErrorTypes.unsupported_stanza_type: + tr("The sent message is of a stanza type that the server does not recognize."), + StreamErrorTypes.unsupported_version: + tr("This server does not support XMPP version 1.0") +} + +var error_type = StreamErrorTypes.undefined_condition +var extra_data = "" + +func parse_from_xml(xml:String): + var parser = XMLParser.new() + parser.open_buffer(xml.to_utf8_buffer()) + parse_with_parser(parser) + +func parse_with_parser(parser:XMLParser): + + # Read the error element + if parser.read() == OK and parser.get_node_name() == "stream:error": + if parser.is_empty(): + error_type = 0 + + # Read the error inside... + if parser.read() == OK: + var error_name = parser.get_node_name().replace("-", "_") + error_type = StreamErrorTypes[error_name] + if !parser.is_empty() and parser.read() == OK: + extra_data = parser.get_node_data() + return OK + +func stanza() -> String: + var stanza = [] + + stanza.append("") + + var error_name = error_name_for_enum(error_type) + + if !error_type.empty(): + stanza.append("<"+error_name) + stanza.append("xmlns='urn:ietf:params:xml:ns:xmpp-streams'") + if extra_data.empty(): + stanza.append("/>") + else: + stanza.append(">"+extra_data+"") + stanza.append("") + + return stanza.join(" ") + +func error_name_for_enum(value :int): + var error_name = "" + for i in StreamErrorTypes.keys(): + if StreamErrorTypes[i] == value: + error_name = str(i).replace("_", "-") + return error_name +### +# Human friendly error messages for the error dialogue. +### +func human_friendly_error_message(): + return humane_error_messages[error_type] diff --git a/client/xmpp/xmpp_stream_features.gd b/client/xmpp/xmpp_stream_features.gd new file mode 100644 index 0000000..b763551 --- /dev/null +++ b/client/xmpp/xmpp_stream_features.gd @@ -0,0 +1,93 @@ +### +# This file is part of Godot XMPP Client +# SPDX-FileCopyrightText: 2020 Wolthera van Hövell tot Westerflier +# +# SPDX-License-Identifier: MIT +### + +extends Resource + +class_name XMPPStreamFeatures + +### +# Parser for stream features. +## + +@export var xml_string:String = "": + set = parse_from_xml + +@export var parsedDictionary:Dictionary = {} + +enum FeatureReq{ + NO_FEATURE, + NO_REQ, + OPTIONAL, + REQUIRED +} + +### +# Remove the entry as it's done. +### +func feature_done(feature:String): + parsedDictionary.erase(feature) + +func parse_from_xml(xml:String): + + var parser = XMLParser.new() + + parser.open_buffer(xml.to_utf8_buffer()) + parser.read() + + parse_children(parser, parser.get_node_name()) + +func parse_children(parser:XMLParser, _parent_name:String): + parsedDictionary = {} + while (parser.read() == OK): + + if (parser.get_node_type()== XMLParser.NODE_ELEMENT): + var node_name = parser.get_node_name() + if !parser.is_empty(): + + match(node_name): + "mechanisms": + parsedDictionary[node_name] = parse_option_list(parser, node_name, "mechanism") + "compression": + parsedDictionary[node_name] = parse_option_list(parser, node_name, "method") + _: + parsedDictionary[node_name] = parse_requirement(parser, node_name) + else: + parsedDictionary[node_name] = FeatureReq.NO_REQ + # else: + # print("Unhandled xml node type: "+str(parser.get_node_type())) + +func parse_requirement(parser:XMLParser, parent_name:String): + var required = self.FeatureReq.NO_REQ + while (parser.read() == OK): + + if (parser.get_node_type()== XMLParser.NODE_ELEMENT): + var node_name = parser.get_node_name() + if parser.is_empty(): + if node_name == "required": + return self.FeatureReq.REQUIRED + elif node_name == "optional": + return self.FeatureReq.OPTIONAL + + elif(parser.get_node_type()== XMLParser.NODE_ELEMENT_END): + var node_name = parser.get_node_name() + if node_name == parent_name: + return required + +func parse_option_list(parser:XMLParser, parent_name:String, option_name:String): + var options = [] + + while (parser.read() == OK): + var _result + if (parser.get_node_type()== XMLParser.NODE_ELEMENT): + if parser.get_node_name() == option_name: + _result =parser.read() + if (parser.get_node_type()== XMLParser.NODE_TEXT): + options.append(parser.get_node_data()) + _result = parser.read() + elif(parser.get_node_type()== XMLParser.NODE_ELEMENT_END): + if parser.get_node_name() == parent_name: + return options diff --git a/create-certificate.sh b/create-certificate.sh new file mode 100755 index 0000000..124464c --- /dev/null +++ b/create-certificate.sh @@ -0,0 +1,155 @@ +#!/bin/bash +# +# Script to create certificate +# +# Copyright (C) 2023 AleaJactaEst +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Example : +# ./create-certificate.sh + +declare DEBUG=0 +declare VERBOSE=0 +declare HELP=0 +declare WORKDIR="$(dirname $(readlink -f $0))" +declare OPENSSLBIN="openssl" +declare CERTDIR="$WORKDIR/.cert" + +function msg_debug() +{ + if [ $DEBUG -ne 0 ] + then + echo "### DEBUG : $*" >&2 + fi +} + +function msg_info() +{ + echo "--- INFO : $*" >&2 +} + +function msg_error() +{ + echo "*** ERROR : $*" >&2 +} + +function byebye() +{ + local CODE=$? + if [ $CODE -ne 0 ] + then + msg_error "return code:$CODE" + else + msg_info "End" + fi + exit $CODE +} + +while getopts hdvs:c: flag +do + case "${flag}" in + h) HELP=1;; + d) DEBUG=1;; + v) VERBOSE=1;; + s) OPENSSLBIN=${OPTARG};; + c) CERTDIR=${OPTARG};; + *) HELP=1;; + esac +done + +if [[ $HELP -ne 0 ]] +then + cat << EOF +$(basename $0) [Option] : Donwload Launch Godot + Option: + -h : Show help + -d : Show debug message + -v : Show verbose message + -s : localization openssl + -c : directory where certificate are created +EOF + exit 1 +fi + +trap byebye EXIT + +msg_info "Start" +msg_debug "WORKDIR:$WORKDIR" + +mkdir -p $CERTDIR + +msg_info "Clean old file" +rm -f $CERTDIR/ca-cert.pem $CERTDIR/ca-db-index.attr $CERTDIR/ca-db-index.attr.old $CERTDIR/ca-db-index.old $CERTDIR/ca-db-serial $CERTDIR/ca-db-serial.old $CERTDIR/ca-key.pem $CERTDIR/client-key.pem $CERTDIR/client.csr $CERTDIR/server-key.pem $CERTDIR/server.csr $CERTDIR/server-cert.pem $CERTDIR/client-cert.pem $CERTDIR/01.pem $CERTDIR/02.pem +rm -f $CERTDIR/ca-db-index $CERTDIR/ca.conf +ls -l $CERTDIR + +msg_info "Restart index certificate" +touch $CERTDIR/ca-db-index +echo 01 > $CERTDIR/ca-db-serial +ls -l $CERTDIR + +msg_info "Create CA config" +cat << EOF > $CERTDIR/ca.conf +[ ca ] +default_ca = ca_default + +[ ca_default ] +dir = $CERTDIR/ +certs = \$dir +new_certs_dir = \$dir +database = $CERTDIR/ca-db-index +serial = $CERTDIR/ca-db-serial +RANDFILE = $CERTDIR/ca-db-rand +certificate = $CERTDIR/ca-cert.pem +private_key = $CERTDIR/ca-key.pem +default_days = 365 +default_crl_days = 365 +default_md = sha256 +preserve = no +policy = generic_policy + +[ generic_policy ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +EOF + +msg_info "Certificate Authority" +$OPENSSLBIN req -nodes -x509 -newkey rsa:2048 -days 365 -keyout $CERTDIR/ca-key.pem -out $CERTDIR/ca-cert.pem -subj "/C=EU/ST=France/L=Paris/O=Khaganat/OU=FR/CN=khanagat.org" || exit 2 + +msg_info "Server Certificate" +$OPENSSLBIN req -nodes -new -newkey rsa:2048 -keyout $CERTDIR/server-key.pem -out $CERTDIR/server.csr -subj "/C=EU/ST=France/L=Paris/O=Khaganat/OU=FR/CN=server.khanagat.org" || exit 2 + +msg_info "Sign Server Certificate" +$OPENSSLBIN ca -config $CERTDIR/ca.conf -days 365 -in $CERTDIR/server.csr -out $CERTDIR/server-cert.pem -batch || exit 2 + +msg_info "Client Certificate" +$OPENSSLBIN req -nodes -new -newkey rsa:2048 -keyout $CERTDIR/client-key.pem -out $CERTDIR/client.csr -subj "/C=EU/ST=France/L=Paris/O=Khaganat/OU=FR/CN=client.khanagat.org" || exit 2 + +msg_info "Sign Client Certificate" +$OPENSSLBIN ca -config $CERTDIR/ca.conf -days 365 -in $CERTDIR/client.csr -out $CERTDIR/client-cert.pem -batch || exit 2 + +msg_info "Publish certificate" + +#cp $CERTDIR/client-cert.pem src/certs/ +#cp $CERTDIR/client-key.pem src/certs/ +#cp $CERTDIR/server-cert.pem src/certs/ +#cp $CERTDIR/server-key.pem src/certs/ + diff --git a/server/src/main.rs b/server/src/main.rs index be9e395..dcdf59f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -668,6 +668,14 @@ struct Cli { /// Loop to force update #[arg(short, long, default_value_t = 10)] forceupdate: u8, + + /// Server Key + #[arg(short = 'k', long, default_value = "server-key.pem")] + serverkey: String, + + /// Server Certificate + #[arg(short = 'm', long, default_value = "server-cert.pem")] + servercert: String, } /* diff --git a/start-bazar-fake-xmpp-server.sh b/start-bazar-fake-xmpp-server.sh new file mode 100755 index 0000000..b3f48e0 --- /dev/null +++ b/start-bazar-fake-xmpp-server.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +declare WORKDIR="$(dirname $(readlink -f $0))" +declare OPENFIREDIR="$WORKDIR/.xmpp" + +echo "WORKDIR:$WORKDIR" +echo "OPENFIREDIR:$OPENFIREDIR" +docker pull sameersbn/openfire:3.10.3-19 + +mkdir -p $OPENFIREDIR +docker run --name openfire \ + --publish 9090:9090 --publish 5222:5222 --publish 7777:7777 \ + --volume $OPENFIREDIR:/var/lib/openfire \ + sameersbn/openfire:3.10.3-19 + +docker start openfire diff --git a/start-bazar-server.sh b/start-bazar-server.sh index 3428e91..b76a3e5 100755 --- a/start-bazar-server.sh +++ b/start-bazar-server.sh @@ -26,6 +26,7 @@ declare BUILD=0 declare WORKDIR="$(dirname $(readlink -f $0))" declare RUSTDIR="$WORKDIR/.rust" declare SERVERDIR="$WORKDIR/server" +declare CERTDIR="$WORKDIR/.cert" declare OPTIONS="" declare LISTEN="" declare PORT="" @@ -53,7 +54,7 @@ function byebye() local CODE=$? if [ $CODE -ne 0 ] then - msg_error "return code:$code" + msg_error "return code:$CODE" else msg_info "End" fi @@ -95,6 +96,12 @@ trap byebye EXIT msg_info "Start" msg_debug "WORKDIR:$WORKDIR" +if [[ (! -f $CERTDIR/server-cert.pem) || (! -f $CERTDIR/server-key.pem) ]] +then + msg_error "Missing certificate" + exit 2 +fi + if [ $DEBUG -ne 0 ] then OPTIONS="$OPTIONS -d" @@ -140,6 +147,6 @@ then fi #echo "OPTIONS:$OPTIONS" -$SERVERDIR/target/debug/server $OPTIONS +$SERVERDIR/target/debug/server $OPTIONS --serverkey $CERTDIR/server-key.pem --servercert $CERTDIR/server-cert.pem # END (call function byebye)