Toastie 1 month ago
commit
0341d370e1
9 changed files with 208 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 20 0
      Dockerfile
  3. 23 0
      README.md
  4. 7 0
      data/config.ini.example
  5. 9 0
      data/getToken.py
  6. 53 0
      data/httpgetpost-debug.py
  7. 78 0
      data/postmsg.py
  8. 6 0
      data/run.sh
  9. 11 0
      docker-compose.yml

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+data/config.ini

+ 20 - 0
Dockerfile

@@ -0,0 +1,20 @@
+FROM ubuntu:focal
+
+RUN \
+  apt-get update && \
+  apt-get install --yes \
+    python3 \
+    python3-pip \
+    python3-venv \
+    wget
+
+RUN \
+  python3 -m venv /opt/http2xmpp && \
+  . /opt/http2xmpp/bin/activate && \
+  pip install wheel && \
+  pip install slixmpp
+
+EXPOSE 8080
+USER 1000
+
+CMD /opt/http2xmpp/server/run.sh

+ 23 - 0
README.md

@@ -0,0 +1,23 @@
+# http2xmpp
+
+Simple Web API to send xmpp messages via http post requests.
+
+## Configuration
+  - The xmpp sender and receivers are configured in the `config.ini` file.
+  - Section ACCOUNT holds the jid (jabber identifier) and password of the user (bot) supposed to send messages.
+  - Section RECEIVER holds a 64 character alpha-numeric bearer tokens used for authorziations. To each token exactly one receiver jid must be assigned.
+
+### Get started
+  - Clone the repo and navigate in the data directory: `cd data`
+  - Copy the example configuration file: `cp config.ini.example config.ini`
+  - Add some bearer tokens to the config files: `./getToken.py >> config.ini`
+  - Setup the sender jid and password, delete the example RECEIVERS and update your own: `vim config.ini`
+  - Start the container: `docker-compose up -d`
+
+### Test 
+```
+curl -i http://localhost:8010 \
+     -H "Authorization: Bearer 2vAWHgdTn74Rb2D4uU5IvyhSKF4xDYGVnu6D8syGU7Wixx4dXfmvxubCMwOxNKWQ" \
+     -X POST -H "Content-Type: text/plain" \
+     --data "put your message here"
+```

+ 7 - 0
data/config.ini.example

@@ -0,0 +1,7 @@
+[ACCOUNT]
+jid = bot@im.example.com
+password = PutYourPasswordHere
+
+[RECIPIENT]
+2vAWHgdTn74Rb2D4uU5IvyhSKF4xDYGVnu6D8syGU7Wixx4dXfmvxubCMwOxNKWQ = johndoe@im.example.com
+6zDv47ZseB5JnV1issJgbCW7VbAUIhDg3I3HtTiscuVobC9g38j4NY2sSmDk5Iaz = janedoe@im.example.net

+ 9 - 0
data/getToken.py

@@ -0,0 +1,9 @@
+#!/usr/bin/python3
+
+
+import string
+import secrets
+alphanum = string.ascii_letters + string.digits
+token = ''.join(secrets.choice(alphanum) for i in range(64))
+print(token)
+

+ 53 - 0
data/httpgetpost-debug.py

@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+"""
+Very simple HTTP server in python for logging requests
+Usage::
+    ./server.py [<port>]
+"""
+
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import logging
+
+
+
+class S(BaseHTTPRequestHandler):
+    def _set_response(self):
+        self.send_response(200)
+        self.send_header('Content-type', 'text/html')
+        self.end_headers()
+
+    def do_GET(self):
+        logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers))
+        self._set_response()
+        self.wfile.write("GET request for {}".format(self.path).encode('utf-8'))
+
+    def do_POST(self):
+        content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
+        post_data = self.rfile.read(content_length) # <--- Gets the data itself
+        logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
+                str(self.path), str(self.headers), post_data.decode('utf-8'))
+        self._set_response()
+        self.wfile.write("POST request for {}".format(self.path).encode('utf-8'))
+
+def run(server_class=HTTPServer, handler_class=S, port=8080):
+    logging.basicConfig(level=logging.INFO)
+    server_address = ('', port)
+    httpd = server_class(server_address, handler_class)
+    logging.info('Starting httpd...\n')
+    try:
+        httpd.serve_forever()
+    except KeyboardInterrupt:
+        pass
+    httpd.server_close()
+    logging.info('Stopping httpd...\n')
+
+
+if __name__ == '__main__':
+    from sys import argv
+
+
+    if len(argv) == 2:
+        run(port=int(argv[1]))
+    else:
+        run()

+ 78 - 0
data/postmsg.py

@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+import logging
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import configparser
+import slixmpp
+
+config = configparser.ConfigParser()
+config.read("config.ini") # <--- Read configfile in dictionary
+
+def getRcpt(token): # <--- lookup recipient for token in config
+    if token in config['RECIPIENT']:
+        rcpt = config['RECIPIENT'][token]
+        return rcpt
+    else:
+        return "" # <--- empty string / FALSE when no token was found
+
+
+
+class S(BaseHTTPRequestHandler):
+
+    def _set_response(code):
+        self.send_header('Content-type', 'text/plain')
+        self.send_response(code)
+        self.end_headers()
+
+    def do_POST(self):
+        token = self.headers['Authorization'][-64:]
+        if token.isalnum(): 
+            logging.info('Valid token received.')
+            content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
+            post_data = self.rfile.read(content_length) # <--- Gets the data itself
+            msg = post_data.decode('utf-8')
+            rcpt = getRcpt(token) # <--- lookup recipient for token in config 
+            if rcpt:
+                sendMsg(rcpt, msg)
+                self._set_response(202) # <--- HTTP Accepted, message sent + posibly received
+            else:
+                self._set_response(401) # <--- HTTP Unauthorized, no token found
+        else:
+            self._set_response(400) # <--- HTTP Bad Request, token contains special characters 
+
+
+
+class SendMsgBot(slixmpp.ClientXMPP):
+    def __init__(self, jid, password, recipient, message):
+        slixmpp.ClientXMPP.__init__(self, jid, password)
+        self.recipient = recipient
+        self.msg = message
+        # Event session_start event is triggered when connection is ready
+        self.add_event_handler("session_start", self.start)
+
+    async def start(self, event):
+        self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat')
+        self.disconnect()
+
+
+def sendMsg(rcpt, msg):
+    jid = config['ACCOUNT']['jid']
+    password = config['ACCOUNT']['password']
+    logging.info('Sending message.')
+    xmpp = SendMsgBot(jid, password, rcpt, msg)
+    xmpp.connect()
+    xmpp.process(forever=False)
+    del xmpp
+ 
+
+def run(server_class=HTTPServer, handler_class=S, port=8080):
+    server_address = ('', port)
+    httpd = server_class(server_address, handler_class)
+    logging.info('Starting server...')
+    try:
+        httpd.serve_forever()
+    except KeyboardInterrupt:
+        pass
+    httpd.server_close()
+
+run()

+ 6 - 0
data/run.sh

@@ -0,0 +1,6 @@
+#/bin/sh
+
+cd /opt/http2xmpp/server && \
+. ../bin/activate && \
+/opt/http2xmpp/server/postmsg.py
+

+ 11 - 0
docker-compose.yml

@@ -0,0 +1,11 @@
+version: '2' 
+services:
+  http2xmpp:
+    build: .
+    image: toastie89/http2xmpp
+    container_name: http2xmpp
+    hostname: http2xmpp 
+    ports:
+      - "8010:8080"
+    volumes:
+      - ./data:/opt/http2xmpp/server:ro