diff options
author | Jonas Wielicki <j.wielicki@sotecware.net> | 2018-04-20 12:41:57 +0200 |
---|---|---|
committer | Jonas Wielicki <j.wielicki@sotecware.net> | 2018-04-20 12:41:57 +0200 |
commit | e3550e83216719cca2e57a350be24ac15ecc9e54 (patch) | |
tree | 1174a6ef3411946d711d9d3e307bc5c21fe7991e |
Initial version (it works!)
-rw-r--r-- | README.rst | 20 | ||||
-rwxr-xr-x | echoz.sed | 102 | ||||
-rwxr-xr-x | echoz.sh | 15 |
3 files changed, 137 insertions, 0 deletions
diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..17eea4a --- /dev/null +++ b/README.rst @@ -0,0 +1,20 @@ +XMPP Echo Bot +============= + +Do you know that situation, you really really need an XMPP echo bot, but you don’t have access to high-level tools like `Python <https://github.com/horazont/aioxmpp>`_ to write one? All you have is `openssl`, `bash`, `dig`, `stdbuf` and `sed`? Then this tool is for you. + +This is an XMPP echo bot written in (mostly) sed. Bash is used to do the pre-authentication setup (look up DNS records, establish TLS via ``openssl s_client``). sed processes the XML stream and handles all interaction with the server on the XMPP level. Yes, this kinda parses XML in sed. + +Tricks and shortcuts +-------------------- + +* We use ``tr`` to convert ``>`` to ``\n`` -- since sed is line (or NUL) based, there’s not really another way to parse XMPP XML (which generally never contains newlines) with sed. +* TLS is handled outside of sed for similar reasons. And to keep my sanity (some people might question whether I still have any bit of sanity left). +* Likewise, SRV lookup and composition of the authentication data is entirely handled in bash. This also means that only PLAIN SASL authentication is supported -- SCRAM requires a level of interactivity which would be extremely hard to achieve in sed (not impossible though; we would "just" have to implement base64 and sha1-hmac in sed). + +Usage +----- + +:: + + ./echoz.sh user@domain password diff --git a/echoz.sed b/echoz.sed new file mode 100755 index 0000000..2a156be --- /dev/null +++ b/echoz.sed @@ -0,0 +1,102 @@ +#!/bin/sed -unrf +# read config into hold buffer +h; +# use domain as to +s#^\S+@(\S+)\s.+$#<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0" to="\1">#; +p;n; + +:wait-for-plain; +/^PLAIN<\/mechanism$/bauth-with-plain; +n; +bwait-for-plain; + +:auth-with-plain; +# load config +g; +s#^\S+\s+(\S+)$#<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>\1</auth>#; +p;n; +bwait-for-sasl-result; + +:wait-for-sasl-result; +/^<success\s+xmlns=['"]urn:ietf:params:xml:ns:xmpp-sasl['"]/bsasl-success; +/^<failure\s+xmlns=['"]urn:ietf:params:xml:ns:xmpp-sasl['"]/bsasl-failure; +n; +bwait-for-sasl-result; + +:sasl-success; +# restart stream: load config and send stream header +g; +s#^\S+@(\S+)\s.+$#<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0" to="\1">#; +p;n; +bwait-for-bind; + +:sasl-failure; +s#^.+$#</stream:stream>#; +q1; + +:wait-for-bind; +/^<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/bbind; +n; +bwait-for-bind; + +:bind; +s#^(.+)$#<iq type='set' id='sed-bind'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>#;p;n; +bwait-for-bind-result; + +:wait-for-bind-result; +/<iq.+id=['"]sed-bind['"]/bprocess-bind-result; +n; +bwait-for-bind-result; + +:process-bind-result; +/type=["']error["']/bexit-with-error; +bsend-presence; + +:exit-with-error; +s#^.+$#</stream:stream>#;p;q1; + +:send-presence; +s#^.+$#<presence/>#;p;n; +bmain-loop; + +:main-loop; +/<message/bhandle-message; +n; +bmain-loop; + +:handle-message; +/type='chat'/bhandle-chat-message; +bmain-loop; + +:handle-chat-message; +# prepare header; store it in hold space first +h; +# extract new to address +s#^.*from=(['"][^'"]+?['"]).*$#to=\1\n#; +# add copy of header and extrat new from address +G; +s#^(.+)\n.+to=(['"][^'"]+?['"]).*$#\1 from=\2#; +# write header +s#^(.+)$#<message type="chat" \1>#; +# store result in hold space +h; +bmessage-search-body-loop; + +:message-search-body-loop; +# drop message if end-of-message before body +\#</message#bmain-loop; +\#<body#bmessage-collect-body; +n; +bmessage-search-body-loop; + +:message-collect-body; +# next line must contain body +n; +s#^(.+)</body#<body>\1</body>#; +# append result to hold space +H; +# load full hold space and append </message> and send +g; +s#^(.+)$#\1</message>#; +p;n; +bmain-loop; diff --git a/echoz.sh b/echoz.sh new file mode 100755 index 0000000..2aff537 --- /dev/null +++ b/echoz.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euf +thisdir=$(dirname $0) +pipename="${XDG_RUNTIME_DIR:-/tmp}/echoz-$$.pipe" +jid="$1" +password="$2" +username="$(echo "$jid" | cut -d'@' -f1)" +domain="$(echo "$jid" | cut -d'@' -f2)" +srv="$( ( dig +short SRV "_xmpp-client._tcp.$domain" || echo "0 0 5222 $domain" ) | sort -n)" +host="$(echo "$srv" | cut -d' ' -f4)" +port="$(echo "$srv" | cut -d' ' -f3)" +authstr="$(echo -ne "\0$username\0$password" | base64)" +rm -f $pipename +mkfifo $pipename +stdbuf -i0 -o0 openssl s_client -starttls xmpp -xmpphost $domain -connect $host:$port -quiet < $pipename | (echo -ne "$jid $authstr\n"; stdbuf -o0 tr '>\n' '\n\001') | stdbuf -o0 $thisdir/echoz.sed | stdbuf -o0 tr -d '\n' | stdbuf -o0 tr '\001' '\n' > $pipename |