главная|main page

состояние|status

блог|blog

файлы|files

программы|software

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Wielicki <j.wielicki@sotecware.net>2018-04-20 12:41:57 +0200
committerJonas Wielicki <j.wielicki@sotecware.net>2018-04-20 12:41:57 +0200
commite3550e83216719cca2e57a350be24ac15ecc9e54 (patch)
tree1174a6ef3411946d711d9d3e307bc5c21fe7991e
Initial version (it works!)
-rw-r--r--README.rst20
-rwxr-xr-xechoz.sed102
-rwxr-xr-xechoz.sh15
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