bazar_alea/client/Stream/Stream.gd

965 lines
33 KiB
GDScript3
Raw Normal View History

2023-12-17 00:03:48 +00:00
extends Node
# Stream XMPP
# Author : AleaJactaEst
#
# https://xmpp.org/extensions/xep-0178.html
# https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml
2023-12-19 00:18:26 +00:00
# https://datatracker.ietf.org/doc/html/rfc6120#section-6.5
2023-12-17 00:03:48 +00:00
# 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
2023-12-18 20:25:25 +00:00
enum XMPPState {
NONE, # Nothing is configured
TRY_CONNECT_SERVER, # Try to connect to server
CONNECTED_SERVER, # Confirmed we are connected to server
INTIALIZE, # Initialize first connection with server
INTIALIZED, # when initialize is finished
FEATURE_SERVER, # We received Feature (TLS ?)
START_TLS, # We start TLS
WAIT_TLS, # Wait TLS Done
MISSING_TLS, # Missing feature TLS -> wait 30min and retry
STARTED_TLS, # TLS connection is done
2023-12-18 21:53:26 +00:00
SELECT_MECHANISM_AUTHENTICATE,
2023-12-19 00:18:26 +00:00
AUTHENTICATE_STEP_PLAIN, # Select mechanism authenticate PLAIN
AUTHENTICATE_STEP_DIGEST_MD5,
# AUTHENTICATE_STEP_1, # Launch authenticate
# AUTHENTICATE_STEP_2, # Launch authenticate
# AUTHENTICATE_STEP_3, # Launch authenticate
# AUTHENTICATE_CHECK_RESULT, # Last time check result
2023-12-18 21:53:26 +00:00
AUTHENTICATE_FAILURE,
AUTHENTICATE_SUCCESS,
2023-12-18 20:25:25 +00:00
AUTHENTICATED, # We finished authenticate
2023-12-18 21:53:26 +00:00
NOT_AUTHORIZED, # Not Authorize
ACCOUNT_DISABLED, # Account Disable
}
enum ResultAuthenticate {
NONE, # Authenticate not started
DONE, # Authenticate finished and done
ABORT, # Authenticate finished with error (try other method)
NEXT, # Authenticate need execute other step
}
enum SASLReturn {
SUCCESS,
ABORTED,
ACCOUNT_DISABLED,
CREDENTIALS_EXPIRED,
ENCRYPTION_REQUIRED,
INCORRECT_ENCODING,
INVALID_AUTHZID,
INVALID_MECHANISM,
MALFORMED_REQUEST,
MECHANISM_TOO_WEAK,
NOT_AUTHORIZED,
TEMPORARY_AUTH_FAILURE,
UNKNOWN
2023-12-18 20:25:25 +00:00
}
@export var server_xmpp_name:String = "localhost":
set = set_server_xmpp_name, get = get_server_xmpp_name
2023-12-17 00:03:48 +00:00
2023-12-18 20:25:25 +00:00
func set_server_xmpp_name(value:String):
server_xmpp_name = value
2023-12-17 00:03:48 +00:00
2023-12-18 20:25:25 +00:00
func get_server_xmpp_name() -> String:
return server_xmpp_name
2023-12-17 00:03:48 +00:00
@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
2023-12-18 20:25:25 +00:00
const MAX_WAIT_CONNECTING:float = 60.0
const MAX_WAIT_MISSING_TLS:float = 1800.0
2023-12-18 21:53:26 +00:00
const MAX_WAIT_ACCOUNT_DISABLED:float = 900.0
const MAX_WAIT_NOT_AUTHORIZED:float = 18000.0
2023-12-18 20:25:25 +00:00
2023-12-17 00:03:48 +00:00
var try_connect:int = 0
2023-12-18 20:25:25 +00:00
var count_connecting_time:float = MAX_WAIT_CONNECTING
2023-12-17 00:03:48 +00:00
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
2023-12-18 20:25:25 +00:00
var xmpp_state = XMPPState.NONE
var authentication_methods = []
2023-12-18 21:53:26 +00:00
var selected_mechanism_authenticate:String = ""
2023-12-19 00:18:26 +00:00
#var order_preference_mechanism_authenticate: Array = ['PLAIN']
var order_preference_mechanism_authenticate: Array = ['DIGEST-MD5']
2023-12-18 21:53:26 +00:00
var banned_mechanism_authenticate:Array = []
2023-12-19 00:18:26 +00:00
#DIGEST-MD5, PLAIN, SCRAM-SHA-512-PLUS, SCRAM-SHA-512, SCRAM-SHA-256-PLUS, SCRAM-SHA-256, SCRAM-SHA-1-PLUS, SCRAM-SHA-1, X-OAUTH2
var auhtentification_step:int = 0
var auhtentification_challenge:Array = []
2023-12-18 21:53:26 +00:00
2023-12-18 20:25:25 +00:00
2023-12-19 00:18:26 +00:00
#func reinit_stream():
# if ssl_peer != null:
# ssl_peer.disconnect_from_stream()
# else:
# ssl_peer = StreamPeerTLS.new()
# if tcp_peer != null:
# tcp_peer.disconnect_from_host()
# else:
# tcp_peer = StreamPeerTCP.new()
# tgt_peer = tcp_peer
## start_stream()
# stream_status = self.StreamState.START
2023-12-17 00:03:48 +00:00
2023-12-18 20:25:25 +00:00
func _init() -> void:
2023-12-17 00:03:48 +00:00
var language = OS.get_locale()
set_locale(language)
tgt_peer = tcp_peer
2023-12-18 20:25:25 +00:00
# reinit_stream()
func _process(delta) -> void:
2023-12-19 00:18:26 +00:00
#print(server_xmpp_name, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status, " / xmpp_state:", XMPPState.keys()[xmpp_state])
2023-12-18 20:25:25 +00:00
if xmppclient == false:
return
# Wait MAX_WAIT_MISSING_TLS (s) if server can't negociate with TLS
if xmpp_state == XMPPState.MISSING_TLS:
if count_connecting_time >= MAX_WAIT_MISSING_TLS:
xmpp_state = XMPPState.NONE
else:
count_connecting_time += delta
2023-12-18 21:53:26 +00:00
elif xmpp_state == XMPPState.NOT_AUTHORIZED:
if count_connecting_time >= MAX_WAIT_NOT_AUTHORIZED:
xmpp_state = XMPPState.NONE
else:
count_connecting_time += delta
elif xmpp_state == XMPPState.ACCOUNT_DISABLED:
if count_connecting_time >= MAX_WAIT_ACCOUNT_DISABLED:
xmpp_state = XMPPState.NONE
else:
count_connecting_time += delta
2023-12-18 20:25:25 +00:00
elif xmpp_state == XMPPState.NONE:
if count_connecting_time >= MAX_WAIT_CONNECTING:
xmpp_state = XMPPState.TRY_CONNECT_SERVER
if ssl_peer.get_status() != StreamPeerTLS.STATUS_DISCONNECTED:
ssl_peer.disconnect_from_stream()
if tcp_peer.get_status() != StreamPeerTCP.STATUS_NONE:
tcp_peer.disconnect_from_host()
else:
count_connecting_time += delta
elif xmpp_state == XMPPState.TRY_CONNECT_SERVER:
if tcp_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.CONNECTED_SERVER
count_connecting_time = MAX_WAIT_CONNECTING
elif count_connecting_time >= MAX_WAIT_CONNECTING:
var res = tcp_peer.connect_to_host(server_xmpp_name, port_number)
if res == OK:
send_msg_debug.emit("Send connect_to_host : ok")
#stream_status = self.StreamState.END
#xmpp_state = XMPPState.CONNECTED_SERVER
count_connecting_time = 0
else:
send_msg_error.emit("Error to connect to XMPP server (return:%d)" % res)
xmpp_state = XMPPState.NONE
count_connecting_time = 0
else:
if (tcp_peer.has_method("poll")):
tcp_peer.poll()
count_connecting_time += delta
elif xmpp_state == XMPPState.CONNECTED_SERVER:
if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if count_connecting_time >= MAX_WAIT_CONNECTING:
count_connecting_time = 0
send_msg_debug.emit("send_tcp_initialize_xmpp")
send_tcp_initialize_xmpp()
else:
count_connecting_time += delta
if (tcp_peer.has_method("poll")):
tcp_peer.poll()
if tcp_peer.get_available_bytes()>0:
var response = tcp_peer.get_string(tcp_peer.get_available_bytes())
send_msg_debug.emit("Stream: response: " + response)
response = remove_stream_header(response)
if not analyze_error(response):
if analyze_feature_starttls(response):
xmpp_state = XMPPState.START_TLS
else:
count_connecting_time = 0
xmpp_state = XMPPState.MISSING_TLS
elif xmpp_state == XMPPState.START_TLS:
if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if count_connecting_time >= MAX_WAIT_CONNECTING:
count_connecting_time = 0
xmpp_state = XMPPState.NONE
else:
count_connecting_time += delta
if (tcp_peer.has_method("poll")):
tcp_peer.poll()
if tcp_peer.get_available_bytes()>0:
var response = tcp_peer.get_string(tcp_peer.get_available_bytes())
send_msg_debug.emit("Stream: response: " + response)
response = remove_stream_header(response)
if response.begins_with("<proceed"):
send_msg_debug.emit("Stream: gotten go ahead for ssl")
var options:TLSOptions = TLSOptions.client_unsafe()
var ssl_succes = ssl_peer.connect_to_stream(tcp_peer, "localhost", options)
if ssl_succes == OK:
send_msg_debug.emit("Stream: switched to SSL!")
xmpp_state = XMPPState.WAIT_TLS
else:
count_connecting_time = 0
xmpp_state = XMPPState.MISSING_TLS
2023-12-18 21:53:26 +00:00
elif response.begins_with("failure"):
2023-12-18 20:25:25 +00:00
send_msg_error.emit(tr("TlS negotiation failed."))
count_connecting_time = 0
xmpp_state = XMPPState.MISSING_TLS
2023-12-18 21:53:26 +00:00
else:
send_msg_error.emit(tr("TlS negotiation failed. (unknow return)"))
count_connecting_time = 0
xmpp_state = XMPPState.MISSING_TLS
2023-12-18 20:25:25 +00:00
elif xmpp_state == XMPPState.WAIT_TLS:
if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if ssl_peer.get_status() == StreamPeerTLS.STATUS_CONNECTED:
xmpp_state = XMPPState.STARTED_TLS
count_connecting_time = MAX_WAIT_CONNECTING
elif ssl_peer.get_status() == StreamPeerTLS.STATUS_HANDSHAKING:
ssl_peer.poll()
if count_connecting_time >= MAX_WAIT_CONNECTING:
count_connecting_time = 0
xmpp_state = XMPPState.MISSING_TLS
else:
count_connecting_time += delta
else:
count_connecting_time = 0
xmpp_state = XMPPState.MISSING_TLS
elif xmpp_state == XMPPState.STARTED_TLS:
if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if ssl_peer.get_status() != StreamPeerTLS.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if count_connecting_time >= MAX_WAIT_CONNECTING:
count_connecting_time = 0
send_msg_debug.emit("send_ssl_initialize_xmpp")
send_ssl_initialize_xmpp()
else:
count_connecting_time += delta
if (ssl_peer.has_method("poll")):
ssl_peer.poll()
if ssl_peer.get_available_bytes()>0:
var response = ssl_peer.get_string(ssl_peer.get_available_bytes())
send_msg_debug.emit("Stream: response: " + response)
response = remove_stream_header(response)
if not analyze_error(response):
if analyze_feature_mechanisms(response):
if authentication_methods.size() > 0:
count_connecting_time = MAX_WAIT_CONNECTING
2023-12-18 21:53:26 +00:00
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
2023-12-18 20:25:25 +00:00
#send_msg_debug.emit("AUTHENTICATE_STEP_1")
# elif response.begins_with("<success"):
# stream_status = self.StreamState.STANZA
2023-12-18 21:53:26 +00:00
if xmpp_state == XMPPState.SELECT_MECHANISM_AUTHENTICATE:
select_mechanism_authenticate(authentication_methods)
if selected_mechanism_authenticate == "NONE":
send_msg_error.emit("Stream: Impossible to find mechanism to authenticate")
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
elif selected_mechanism_authenticate == "PLAIN":
xmpp_state = XMPPState.AUTHENTICATE_STEP_PLAIN
2023-12-19 00:18:26 +00:00
auhtentification_step = 0
elif selected_mechanism_authenticate == "DIGEST-MD5":
xmpp_state = XMPPState.AUTHENTICATE_STEP_DIGEST_MD5
auhtentification_step = 0
auhtentification_challenge = []
2023-12-18 21:53:26 +00:00
if xmpp_state == XMPPState.AUTHENTICATE_STEP_PLAIN:
2023-12-19 00:18:26 +00:00
# https://datatracker.ietf.org/doc/html/rfc4616
2023-12-18 20:25:25 +00:00
if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if count_connecting_time >= MAX_WAIT_CONNECTING:
count_connecting_time = 0
2023-12-18 21:53:26 +00:00
negotiate_ssl_authenticate_plain()
2023-12-18 20:25:25 +00:00
else:
count_connecting_time += delta
if (ssl_peer.has_method("poll")):
ssl_peer.poll()
if ssl_peer.get_available_bytes()>0:
var response = ssl_peer.get_string(ssl_peer.get_available_bytes())
send_msg_debug.emit("Stream: response: " + response)
response = remove_stream_header(response)
2023-12-18 21:53:26 +00:00
var res:SASLReturn = get_result_authenticate(response)
if res == SASLReturn.SUCCESS:
2023-12-18 20:25:25 +00:00
count_connecting_time = 0
xmpp_state = XMPPState.AUTHENTICATED
2023-12-18 21:53:26 +00:00
elif res == SASLReturn.NOT_AUTHORIZED:
count_connecting_time = 0
xmpp_state = XMPPState.NOT_AUTHORIZED
elif res == SASLReturn.ACCOUNT_DISABLED:
count_connecting_time = 0
xmpp_state = XMPPState.ACCOUNT_DISABLED
elif res == SASLReturn.NOT_AUTHORIZED:
count_connecting_time = 0
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
elif res == SASLReturn.INVALID_MECHANISM:
count_connecting_time = 0
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
else:
count_connecting_time = 0
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
2023-12-19 00:18:26 +00:00
if xmpp_state == XMPPState.AUTHENTICATE_STEP_DIGEST_MD5:
# https://datatracker.ietf.org/doc/html/rfc2831
if tcp_peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
xmpp_state = XMPPState.NONE
count_connecting_time = 0
return
if count_connecting_time >= MAX_WAIT_CONNECTING:
count_connecting_time = 0
negotiate_ssl_authenticate_digest_md5()
else:
count_connecting_time += delta
if (ssl_peer.has_method("poll")):
ssl_peer.poll()
if ssl_peer.get_available_bytes()>0:
var response = ssl_peer.get_string(ssl_peer.get_available_bytes())
send_msg_debug.emit("Stream: response: " + response)
response = remove_stream_header(response)
if response.begins_with("<challenge"):
var data:String = get_xml_data(response, ["challenge"])
#var xmlns:String = get_xml_attribute(response, ["challenge"], "xmlns")
print(auhtentification_step, " / ", data)
auhtentification_challenge.append(data)
if ! negotiate_ssl_authenticate_digest_md5():
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
return
else:
var res:SASLReturn = get_result_authenticate(response)
if res == SASLReturn.SUCCESS:
count_connecting_time = 0
xmpp_state = XMPPState.AUTHENTICATED
elif res == SASLReturn.NOT_AUTHORIZED:
count_connecting_time = 0
xmpp_state = XMPPState.NOT_AUTHORIZED
elif res == SASLReturn.ACCOUNT_DISABLED:
count_connecting_time = 0
xmpp_state = XMPPState.ACCOUNT_DISABLED
elif res == SASLReturn.NOT_AUTHORIZED:
count_connecting_time = 0
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
elif res == SASLReturn.INVALID_MECHANISM:
count_connecting_time = 0
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
else:
count_connecting_time = 0
xmpp_state = XMPPState.SELECT_MECHANISM_AUTHENTICATE
banned_mechanism_authenticate.append(selected_mechanism_authenticate)
func get_xml_data(response:String, nodes:Array) -> String:
var node_name:String = ""
var data:String = ""
var parser:XMLParser = XMLParser.new()
var cur:Array = []
parser.open_buffer(response.to_utf8_buffer())
while (parser.read() == OK):
if (parser.get_node_type()== XMLParser.NODE_ELEMENT):
node_name = parser.get_node_name()
cur.append(node_name)
print("node_name:", node_name)
var attributes_dict = {}
for idx in range(parser.get_attribute_count()):
attributes_dict[parser.get_attribute_name(idx)] = parser.get_attribute_value(idx)
print("The ", node_name, " element has the following attributes: ", attributes_dict)
elif (parser.get_node_type()== XMLParser.NODE_TEXT):
var correctpath:bool = true
print(nodes.size() , " != ", cur.size())
if nodes.size() != cur.size():
continue
for i in range(0, nodes.size()):
print(cur[i] , " != ", nodes[i])
if cur[i] != nodes[i]:
correctpath = false
break
if correctpath:
data = parser.get_node_data()
print("data:", data)
return data
elif (parser.get_node_type()== XMLParser.NODE_ELEMENT_END):
cur.pop_back()
return ""
func get_xml_attribute(response:String, nodes:Array, attribute:String) -> String:
var node_name:String = ""
var data:String = ""
var parser:XMLParser = XMLParser.new()
var cur:Array = []
parser.open_buffer(response.to_utf8_buffer())
while (parser.read() == OK):
if (parser.get_node_type()== XMLParser.NODE_ELEMENT):
node_name = parser.get_node_name()
cur.append(node_name)
print("node_name:", node_name)
var attributes_dict = {}
for idx in range(parser.get_attribute_count()):
if parser.get_attribute_name(idx) == attribute:
return parser.get_attribute_value(idx)
elif (parser.get_node_type()== XMLParser.NODE_ELEMENT_END):
cur.pop_back()
return ""
2023-12-18 21:53:26 +00:00
func get_result_authenticate(response:String) -> SASLReturn:
if response.begins_with("<success"):
count_connecting_time = 0
return SASLReturn.SUCCESS
elif response.begins_with("<failure"):
if response.contains("<aborted/>"):
return SASLReturn.ABORTED
elif response.contains("<account-disabled/>"):
return SASLReturn.ACCOUNT_DISABLED
if response.contains("<credentials-expired/>"):
return SASLReturn.CREDENTIALS_EXPIRED
if response.contains("<encryption-required/>"):
return SASLReturn.ENCRYPTION_REQUIRED
if response.contains("<incorrect-encoding/>"):
return SASLReturn.INCORRECT_ENCODING
if response.contains("<invalid-authzid/>"):
return SASLReturn.INVALID_AUTHZID
if response.contains("<invalid-mechanism/>"):
return SASLReturn.INVALID_MECHANISM
if response.contains("<malformed-request/>"):
return SASLReturn.MALFORMED_REQUEST
if response.contains("<mechanism-too-weak/>"):
return SASLReturn.MECHANISM_TOO_WEAK
if response.contains("<not-authorized/>"):
return SASLReturn.NOT_AUTHORIZED
if response.contains("<temporary-auth-failure/>"):
return SASLReturn.TEMPORARY_AUTH_FAILURE
return SASLReturn.UNKNOWN
2023-12-18 20:25:25 +00:00
func analyze_error(response:String) -> bool:
if response.begins_with("<stream:error"):
var stream_error = XMPPStreamError.new()
stream_error.parse_from_xml(response)
var error_name = stream_error.error_name_for_enum(stream_error.error_type)
var error = tr('A stream error of type "%" occured, or in other words: % (Stream errors cannot be recovered from, the connection will be closed.'.format([error_name, stream_error.human_friendly_error_message()], "%)"))
error.emit(error)
return true
return false
func analyze_feature_starttls(response:String) -> bool:
if response.begins_with("<stream:features"):
var stream_features = XMPPStreamFeatures.new()
stream_features.parse_from_xml(response)
if stream_features.parsedDictionary.has("starttls"):
send_msg_debug.emit("Stream: sending request for ssl")
var request_tls = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
send_tcp_string(request_tls)
stream_status = self.StreamState.TLS
return true
return false
func analyze_feature_mechanisms(response:String) -> bool:
if response.begins_with("<stream:features"):
var stream_features = XMPPStreamFeatures.new()
stream_features.parse_from_xml(response)
if stream_features.parsedDictionary.has("mechanisms"):
print("response:", response)
authentication_methods = stream_features.parsedDictionary["mechanisms"]
if (authentication_methods.size() > 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_ssl_sasl(authentication_methods)
#stream_status = self.StreamState.AUTHENTICATE
return true
#negotiate_ssl_sasl(authentication_methods)
return false
func send_tcp_initialize_xmpp() -> void:
""" Send the stream header.
Needs to be done several times over stream negotiation.
"""
var message:String = "<?xml version='1.0'?>" + \
"<stream:stream" + \
" xmlns=\"jabber:client\"" + \
" version=\"1.0\"" + \
" xmlns:stream=\"http://etherx.jabber.org/streams\"" + \
" to=\"" + server_xmpp_name + "\" " + \
" xml:lang=\"en\"" + \
" > "
# " from=\"" + account_name + "\" " + \
# + locale + "'" +
print(message)
send_tcp_string(message)
func send_ssl_initialize_xmpp() -> void:
""" Send the stream header.
Needs to be done several times over stream negotiation.
"""
var server_name = account_name.split("@")[-1]
var message:String = "<?xml version='1.0'?>" + \
"<stream:stream" + \
" xmlns=\"jabber:client\"" + \
" version=\"1.0\"" + \
" xmlns:stream=\"http://etherx.jabber.org/streams\"" + \
" from=\"" + account_name + "\" " + \
" to=\"" + server_name + "\" " + \
" xml:lang=\"en\"" + \
" > "
# " from=\"" + account_name + "\" " + \
# + locale + "'" +
print(message)
send_ssl_string(message)
2023-12-18 21:53:26 +00:00
func select_mechanism_authenticate(authentication_methods : Array) -> void:
selected_mechanism_authenticate = "NONE"
for item in order_preference_mechanism_authenticate:
if banned_mechanism_authenticate.has(item):
continue
elif authentication_methods.has(item):
selected_mechanism_authenticate = item
break
func negotiate_ssl_authenticate_plain() -> void:
2023-12-19 00:18:26 +00:00
send_msg_debug.emit("Stream: sending request for PLAIN")
2023-12-18 21:53:26 +00:00
var msg:PackedByteArray = PackedByteArray()
2023-12-19 00:18:26 +00:00
# [authzid]
#msg += server_xmpp_name.to_ascii_buffer()
# UTF8NUL
2023-12-18 21:53:26 +00:00
msg.push_back(0)
2023-12-19 00:18:26 +00:00
# authcid
2023-12-18 21:53:26 +00:00
var t = account_name.split("@")[0]
msg += t.to_ascii_buffer()
2023-12-19 00:18:26 +00:00
# UTF8NUL
2023-12-18 21:53:26 +00:00
msg.push_back(0)
2023-12-19 00:18:26 +00:00
# passwd
2023-12-18 21:53:26 +00:00
msg += password.to_ascii_buffer()
var auth_account:String = Marshalls.raw_to_base64(msg)
var request_sasl:String = "<auth" + \
" xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + \
" mechanism='PLAIN'" + \
" >" + auth_account + "</auth>"
send_ssl_string(request_sasl)
2023-12-18 20:25:25 +00:00
2023-12-19 00:18:26 +00:00
func negotiate_ssl_authenticate_digest_md5() -> bool:
send_msg_debug.emit("Stream: sending request for DIGEST-MD5 (step:%d)" % auhtentification_step)
if auhtentification_step == 0:
var request_sasl:String = "<auth" + \
" xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + \
" mechanism='DIGEST-MD5'" + \
" >" + "</auth>"
send_ssl_string(request_sasl)
auhtentification_step += 1
return true
elif auhtentification_step == 1:
var tmp:String = Marshalls.base64_to_utf8(auhtentification_challenge[0])
var tab:Array = tmp.split(",")
var realm:String = ""
var nonce:String = ""
var qop:String = ""
var charset:String = ""
var algorithm:String = ""
print(str(tab))
for item in tab:
var tab2:Array = item.split("=")
var key:String = tab2[0]
var value:String = tab2[1]
if key == "realm":
realm = value.trim_prefix('"').trim_suffix('"')
if key == "nonce":
nonce = value.trim_prefix('"').trim_suffix('"')
elif key == "qop":
qop = value.trim_prefix('"').trim_suffix('"')
elif key == "charset":
charset = value
elif key == "algorithm":
algorithm = value
print(key," = ", value)
print(tmp, " ", qop, " ", charset, " ",nonce)
if qop != "auth":
send_msg_error.emit("Stream: Authenticate digest-md5, qop unknonw (%s)" % qop)
return false
if charset != "utf-8":
send_msg_error.emit("Stream: Authenticate digest-md5, charset unknonw (%s)" % charset)
return false
if algorithm != "md5-sess":
send_msg_error.emit("Stream: Authenticate digest-md5, algorithm unknonw (%s)" % algorithm)
return false
print(tmp)
auhtentification_step += 1
return true
return false
2023-12-18 20:25:25 +00:00
func negotiate_ssl_sasl(authentication_methods : Array) -> bool:
if ( authentication_methods.has("PLAIN")):
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()
var auth_account:String = Marshalls.raw_to_base64(msg)
var request_sasl:String = "<auth" + \
" xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + \
" mechanism='PLAIN'" + \
" >" + auth_account + "</auth>"
send_ssl_string(request_sasl)
return true
else:
send_msg_error.emit("Impossible to authenticate (unknown protocol)")
return false
#
#
#
2023-12-19 00:18:26 +00:00
#func connect_to_server_xmpp() -> void:
# var res = tcp_peer.connect_to_host(server_xmpp_name, port_number)
# if res == OK:
# count_connecting_time = 0
# stream_status = self.StreamState.END
# set_process(true)
# tgt_peer = tcp_peer
#func _process_old(delta) -> void:
# print(server_xmpp_name, ":", port_number, " / get_status:" , tcp_peer.get_status(), " / count_connecting_time:", count_connecting_time, " / stream_status:", stream_status)
# 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))
# print("collect_stanza")
# else:
# print("stream_process")
# stream_process(remove_stream_header(response))
## print(remove_stream_header(response))
# elif stream_status == self.StreamState.END:
# reinit_stream()
# #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
# reinit_stream()
# if tgt_peer.get_status() == StreamPeerTCP.STATUS_NONE and xmppclient:
# print("connect_to_server:", xmppclient)
# connect_to_server()
# xmppclient = false
2023-12-17 00:03:48 +00:00
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())
2023-12-18 20:25:25 +00:00
var res = tcp_peer.connect_to_host(server_xmpp_name, port_number)
2023-12-17 00:03:48 +00:00
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())
2023-12-18 20:25:25 +00:00
func send_tcp_string(stanza:String) ->void:
""" Send a string in the appropriate encoding. """
send_msg_debug.emit("Sending data: '%'".format([stanza], "%"))
tcp_peer.put_data(stanza.to_utf8_buffer())
func send_ssl_string(stanza:String) ->void:
""" Send a string in the appropriate encoding. """
send_msg_debug.emit("Sending data: '%'".format([stanza], "%"))
ssl_peer.put_data(stanza.to_utf8_buffer())
2023-12-17 00:03:48 +00:00
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)
2023-12-19 00:18:26 +00:00
#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 = "<?xml version='1.0'?>" + \
# "<stream:stream" + \
# " xmlns=\"jabber:client\"" + \
# " version=\"1.0\"" + \
# " xmlns:stream=\"http://etherx.jabber.org/streams\"" + \
# " from=\"" + account_name + "\" " + \
# " to=\"" + server_name + "\" " + \
# " xml:lang=\"en\"" + \
# " > "
# # " from=\"" + account_name + "\" " + \
# # + locale + "'" +
# print(message)
# send_string(message)
2023-12-17 00:03:48 +00:00
func remove_stream_header(text :String) -> String:
var index = 0
if text.begins_with("<?") :
# Strip xml header
index = text.find("?>")
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
2023-12-19 00:18:26 +00:00
#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("<stream:error"):
# var stream_error = XMPPStreamError.new()
# stream_error.parse_from_xml(response)
# var error_name = stream_error.error_name_for_enum(stream_error.error_type)
# var error = tr('A stream error of type "%" occured, or in other words: % \n\nStream errors cannot be recovered from, the connection will be closed.'.format([error_name, stream_error.human_friendly_error_message()], "%"))
# error.emit(error)
#
# elif response.begins_with("<stream:features"):
# var stream_features = XMPPStreamFeatures.new()
# stream_features.parse_from_xml(response)
#
# if stream_features.parsedDictionary.has("starttls"):
# send_msg_debug.emit("Stream: sending request for ssl")
# var request_tls = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
# send_string(request_tls)
# stream_status = self.StreamState.TLS
#
# elif stream_features.parsedDictionary.has("mechanisms"):
# print("response:", response)
# var authentication_methods = stream_features.parsedDictionary["mechanisms"]
# if (authentication_methods.size() > 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("<proceed"):
# send_msg_debug.emit("Stream: gotten go ahead for ssl")
# negotiate_tls()
# else:
# send_msg_error.emit(tr("Tls negotiation failed."))
#
# elif stream_status == StreamState.AUTHENTICATE:
# if response.begins_with("<success"):
# stream_status = self.StreamState.STANZA
## start_stream()
# return
# else:
# var parser = XMLParser.new()
## parser.open_buffer(response.to_utf8())
# var failure_text = []
# while parser.read() == OK:
# if parser.get_node_type() == XMLParser.NODE_TEXT:
# failure_text.append(parser.get_node_data())
# print(failure_text)
## failure_text = tr("Authentication failed! %".format([PoolStringArray(failure_text).join(" ")], "%"))
# var msg:String = ""
# for item in failure_text:
# msg += item
# send_msg_error.emit(msg)
# elif stream_status == self.StreamState.START:
# connect_to_server()
# start_stream()
# else:
# print("Mystery stream status: "+str(stream_status))
#func negotiate_tls() -> bool:
# #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
# return true
# else:
# send_msg_error.emit("SSL failed, error %".format([ssl_succes], "%"))
# return false
#
#
#func negotiate_sasl(authentication_methods : Array) -> void:
# if ( authentication_methods.has("PLAIN")):
# 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()
# var auth_account:String = Marshalls.raw_to_base64(msg)
# var request_sasl:String = "<auth" + \
# " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + \
# " mechanism='PLAIN'" + \
# " >" + auth_account + "</auth>"
# send_string(request_sasl)
# else:
# send_msg_error.emit("Impossible to authenticate (unknown protocol)")
# end_stream()
2023-12-17 00:03:48 +00:00
2023-12-19 00:18:26 +00:00
#
#func end_stream() -> void:
# """ End the stream """
# send_msg_debug.emit("Stream: Ending stream")
# send_string("</stream>")
# 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