# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[asyncdispatch, asyncnet, net, strutils, xmltree] import syndicate import getdns import ./protocol when not defined(posix): {.error: "This utility requires POSIX".} proc checkError(r: getdns_return_t) = if r.isBad: quit($r, QuitFailure) type Client = ref object context: SslContext socket: AsyncSocket domain, jid: string proc close(client: Client) = try: close(client.socket) except CatchableError: discard # stupid destroyContext(client.context) proc runStream(client: Client) {.async.} = block: var opening = newXmlTree( "stream:stream", [], { "from": client.jid, "to": client.domain, "version": "1.0", "xml:lang": "en", "xmlns": "jabber:client", "xmlns:stream": "http://etherx.jabber.org/streams", }.toXmlAttributes) var txt = $opening txt.setLen(text.high) txt[text.high] = '>' # XMPP streams are not an XML document await send(client.socket, $opening) proc connect(client: Client) {.async.} = var serviceProtoName = "_xmpps-client._tcp." & client.domain stderr.writeLine "lookup ", serviceProtoName var dns = getdns.newContext(true) defer: context_destroy(dns) dns.setResolutionType(GETDNS_RESOLUTION_STUB) var response: Dict checkError service_sync(dns, serviceProtoName, nil, addr response) if response["status"].int != RESPSTATUS_GOOD: raise newException(IOError, $response) for srvAddr in response["srv_addresses"]: wrapSocket(client.context, client.socket) try: let host = "jabber.spam.works" # srvAddr["domain_name"].bindata.toFqdn port = Port 5223 # srvAddr["port"].int.Port stderr.writeLine "connecting to ", host, ":", port await connect(client.socket, host, port) stderr.writeLine "successfully connected to ", host, ":", port break except CatchableError as e: stderr.writeLine "failed to connect: ", e.msg close(client) proc bootClient(turn: var Turn; jid, password: string; ds: Ref): Client = block: var domains = rsplit(jid, '@', 1) if domains.len != 2: stderr.writeLine "invalid JID ", jid raise newException(ValueError, "invalid JID " & jid) result = Client( context: newContext(protTLSv1, CVerifyNone), socket: newAsyncSocket(), domain: domains[1], jid: jid, ) waitFor runStream(result) waitFor connect(result) waitFor runStream(result) bootDataspace("main") do (root: Ref; turn: var Turn): connectStdio(root, turn) during(turn, root, ?XmppClient) do (jid: string, password: string, ds: Ref): let client = bootClient(turn, jid, password, ds) do: stderr.writeLine "retracting client" close(client) runForever()