jabber_actor/src/jabber_actor.nim

90 lines
2.8 KiB
Nim

# 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()