#!/usr/bin/env python3 """Real TCP/TLS integration test; stdlib only; requires make-demo.sh first.""" import base64 import hashlib import hmac import re import signal import socket import ssl import subprocess import time from pathlib import Path import xml.etree.ElementTree as ET ROOT=Path(__file__).resolve().parents[1] STREAM="" class Client: def __init__(self): self.sock=socket.create_connection(('127.0.0.1',55222),timeout=5) self.buf=b'' self.send(STREAM) assert b'') self.send("") assert b'proceed' in self.until(b'/>') ctx=ssl.create_default_context(cafile=str(ROOT/'demo/cert.pem')) self.sock=ctx.wrap_socket(self.sock,server_hostname='localhost') self.send(STREAM) assert b'PLAIN' in self.until(b'') def send(self,s): self.sock.sendall(s.encode()) def until(self,mark): while mark not in self.buf: chunk=self.sock.recv(65536) if not chunk: raise RuntimeError('unexpected EOF') self.buf+=chunk end=self.buf.index(mark)+len(mark) out,self.buf=self.buf[:end],self.buf[end:] return out def auth(self,user,password,fail=False): encoded=base64.b64encode(('\0'+user+'\0'+password).encode()).decode() self.send(f"{encoded}") if fail: assert b'not-authorized' in self.until(b'');return assert b'success' in self.until(b'/>') self.send(STREAM) assert b'xmpp-bind' in self.until(b'') self.send("smoke") assert f'{user}@localhost/smoke'.encode() in self.until(b'') def close(self): self.sock.close() def account_hash_crosscheck(): record=(ROOT/'demo/data/localhost/accounts/alice.dat').read_text() def value(k): return re.search(rf'{k}\s*=\s*"([^"]+)"',record).group(1) iterations=int(re.search(r'iteration_count\s*=\s*(\d+)',record).group(1)) salted=hashlib.pbkdf2_hmac('sha256',b'alice-test-only',value('salt').encode(),iterations) stored=hashlib.sha256(hmac.new(salted,b'Client Key','sha256').digest()).hexdigest() server=hmac.new(salted,b'Server Key','sha256').hexdigest() assert stored==value('stored_key') and server==value('server_key') p=subprocess.Popen([str(ROOT/'target/release/rust-xmpp'),'serve',str(ROOT/'demo/config.toml'),'--experimental'],cwd=ROOT,stdout=subprocess.PIPE,stderr=subprocess.PIPE) clients=[] try: for _ in range(100): if p.poll() is not None: raise RuntimeError(p.stderr.read().decode()) try: probe=socket.create_connection(('127.0.0.1',55222),timeout=.1);probe.close();break except OSError: time.sleep(.05) else: raise RuntimeError('server did not start') account_hash_crosscheck() a=Client();clients.append(a) a.auth('alice','wrong',fail=True);a.auth('alice','alice-test-only') b=Client();clients.append(b);b.auth('bob','bob-test-only') a.send("");b.send("") b.send("");b.until(b'') a.send("") assert b"type='result'" in a.until(b'') a.send("Hello & привет") xml=b.until(b'');m=ET.fromstring(xml) assert m.attrib['from']=='alice@localhost/smoke' assert m.find('{jabber:client}body').text=='Hello & привет' b.send("Reply") assert ET.fromstring(a.until(b'')).find('{jabber:client}body').text=='Reply' # Forwarding keeps extension namespace declarations from the stanza root. a.send("data") extension=ET.fromstring(b.until(b'')) assert extension.find('{urn:test:extension}payload').text=='data' assert extension.attrib['{http://www.w3.org/XML/1998/namespace}lang']=='ru' a.send("test") assert b'service-unavailable' in a.until(b'') a.send("") assert b'service-unavailable' in a.until(b'') # Passwords must never be accepted on a plaintext stream. raw=socket.create_connection(('127.0.0.1',55222),timeout=3) raw.sendall(STREAM.encode());raw.recv(65536) raw.sendall(b"eA==") while True: chunk=raw.recv(65536) assert b'