extends Node # Stream XMPP # Author : AleaJactaEst # # https://xmpp.org/extensions/xep-0178.html # https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml # Boucle # - non connecte # - connecte en claire # - connecte en TLS # - lance l'authentification # - step authentification # - identifie signal send_msg_debug(message:String) signal send_msg_error(message:String) signal new_stanza(stanza) enum StreamState { END, # Stream is over. Starting with this. START, # Stream hass started. TLS, # Connection has been made and the first header is send. Let's get ssl! AUTHENTICATE, # We have negotiated whether to use TLS, let's authenticate. STANZA # We have authenticated, account is now allowed to send stanzas } var stream_status = StreamState.END @export var server_ip:String = "localhost": set = set_server_ip, get = get_server_ip func set_server_ip(value:String): server_ip = value func get_server_ip() -> String: return server_ip @export var port_number:int = 5222: set = set_port_number, get = get_port_number func set_port_number(value:int): port_number = value func get_port_number() -> int: return port_number @export var account_name:String = "undefined@localhost": set = set_account_name, get = get_account_name func set_account_name(value:String): account_name = value func get_account_name() -> String: return account_name @export var password:String = "undefined": set = set_password, get = get_password func set_password(value:String): password = value func get_password() -> String: return password @export var locale:String = "en": set = set_locale, get = get_locale func set_locale(value:String): locale = value func get_locale() -> String: return locale @export var xmppclient:bool = false: get: return xmppclient set(value): xmppclient = value var try_connect:int = 0 var count_connecting_time:float = 0 var partial_stanza:String = "" var tcp_peer:StreamPeerTCP = StreamPeerTCP.new() var ssl_peer:StreamPeerTLS = StreamPeerTLS.new() var tgt_peer = null var status:StreamState = StreamState.END func _init(): var language = OS.get_locale() set_locale(language) tgt_peer = tcp_peer func _process(delta): #Global.msg_info("TCP:%d", [tcp_peer.get_status()]) print(server_ip, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status) # if tcp_peer.get_status() == StreamPeerTCP.STATUS_NONE and stream_status == self.StreamState.TLS: # #negotiate_tls() # print("negotiate_tls") # pass if (tgt_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED) : print("STATUS_CONNECTED:", delta) if (tgt_peer.has_method("poll")): tgt_peer.poll() if tgt_peer.get_available_bytes()>0: var response = tgt_peer.get_string(tgt_peer.get_available_bytes()) send_msg_debug.emit("Stream: response: " + response) if stream_status == self.StreamState.STANZA: #collect_stanza(remove_stream_header(response)) Global.msg_info("", []) print("collect_stanza") else: print("stream_process") stream_process(remove_stream_header(response)) # print(remove_stream_header(response)) elif stream_status == self.StreamState.END: start_stream() stream_status = self.StreamState.START count_connecting_time = 0 if tgt_peer.get_status() == StreamPeerTCP.STATUS_CONNECTING: print("STATUS_CONNECTING:", delta) count_connecting_time += delta if (tgt_peer.has_method("poll")): tgt_peer.poll() if count_connecting_time > 60: # (1 -> 1s) if it took more than 1s to connect, error print("**** Stream: Stuck connecting, will now disconnect") Global.msg_error("Stream: Stuck connecting, will now disconnect", []) send_msg_debug.emit("Stream: Stuck connecting, will now disconnect") tgt_peer.disconnect_from_host() #interrupts connection to nothing set_process(false) # stop listening for packets try_connect = 20 stream_status = self.StreamState.END if tgt_peer.get_status() == StreamPeerTCP.STATUS_NONE and xmppclient: print("connect_to_server:", xmppclient) connect_to_server() xmppclient = false func connect_to_server(): """ Connect to the server ip and port, and start checking whether there's stream info yet. """ if tcp_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: pass if try_connect == 0 and tcp_peer.get_status() == StreamPeerTCP.STATUS_NONE: #print("-> ", server_ip, ":", port_number, "/" , tcp_peer.get_status()) var res = tcp_peer.connect_to_host(server_ip, port_number) if res == OK: count_connecting_time = 0 stream_status = self.StreamState.END set_process(true) tgt_peer = tcp_peer else: try_connect = 20 else: try_connect -= 1 func send_string(stanza:String) ->void: """ Send a string in the appropriate encoding. """ send_msg_debug.emit("Sending data: '%'".format([stanza], "%")) tgt_peer.put_data(stanza.to_utf8_buffer()) func conv_string_to_PackedByteArray(message:String) -> PackedByteArray: return message.to_ascii_buffer() #func send_PackedByteArray(message:PackedByteArray) -> void: # """ Send a PackedByteArray """ # send_msg_debug.emit("Sending data: '" + message.get_string_from_utf8() + "'") # tgt_peer.put_data(message) func start_stream() -> void: """ Send the stream header. Needs to be done several times over stream negotiation. """ var server_name = account_name.split("@")[-1] var message:String = "" + \ " " # " from=\"" + account_name + "\" " + \ # + locale + "'" + print(message) send_string(message) func remove_stream_header(text :String) -> String: var index = 0 if text.begins_with("") text = text.substr(index+2).strip_edges() # strip stream header var rg = RegEx.new() rg.compile("<\\s?(stream|stream:stream)\\s") var result = rg.search(text) if result: send_msg_debug.emit("Stream: Response header received") index = text.find(">", result.get_end()) text = text.substr(index+1) return text func stream_process(response :String = "") -> void: """ Try to authenticate using SASL. We can currently only do plain. For SCRAM based methods, we need HMAC at the very least. """ if (!response.length() == 0 and stream_status != self.StreamState.STANZA): if response.begins_with(" 0): var tmp:String = "" var sep:String = "" for item in authentication_methods: tmp += sep + item sep = ", " send_msg_debug.emit("Stream: authentication methods: " + tmp) negotiate_sasl(authentication_methods) stream_status = self.StreamState.AUTHENTICATE else: if stream_status == StreamState.TLS: if response.begins_with(" void: #var ssl_peer = StreamPeerTLS.new() # I am unsure how to validate the ssl certificate for an unknown host? #var ssl_succes = FAILED var options:TLSOptions = TLSOptions.client_unsafe() print("accept_stream") #var ssl_succes = ssl_peer.accept_stream(tcp_peer, options) var ssl_succes = ssl_peer.connect_to_stream(tcp_peer, "localhost", options) print("resultat:", ssl_succes) if ssl_succes == OK: send_msg_debug.emit("Stream: switched to SSL!") print("get_status:", ssl_peer.get_status()) # Wait connection done while ssl_peer.get_status() == StreamPeerTLS.STATUS_HANDSHAKING: ssl_peer.poll() print("get_status:", ssl_peer.get_status()) tgt_peer = ssl_peer start_stream() print("get_status:", ssl_peer.get_status()) print("-----") #stream_status == StreamState.START else: send_msg_error.emit("SSL failed, error %".format([ssl_succes], "%")) func negotiate_sasl(authentication_methods : Array) -> void: if (!authentication_methods.has("PLAIN")): end_stream() send_msg_debug.emit("Stream: sending request for plain") var msg:PackedByteArray = PackedByteArray() msg.push_back(0) var t = account_name.split("@")[0] msg += t.to_ascii_buffer() #msg += conv_string_to_PackedByteArray(account_name.split("@")[0]) msg.push_back(0) msg += password.to_ascii_buffer() print("7:", msg) var auth_account:String = Marshalls.raw_to_base64(msg) var request_sasl:String = "" + auth_account + "" send_string(request_sasl) func end_stream() -> void: """ End the stream """ send_msg_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