diff --git a/.gitignore b/.gitignore index 2869ec0..be9d161 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ __pycache__/ *.pyc *.pyo - +.flake8 +.venv/ +test/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 96f4f72..3cc3c3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "python.pythonPath": "/usr/bin/python3", - "python.linting.pylintEnabled": true, - "python.linting.enabled": true + "python.linting.enabled": true, + "python.formatting.provider": "black", + "python.linting.flake8Enabled": true } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..33d6862 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ + + +To obtain a DNS verified certificate for the websockert server: + +certbot certonly -d w02.wrede.ca -d ws.wrede.ca --dns-rfc2136 --dns-rfc2136-credentials /usr/local/etc/letsencrypt/certbot_dns_rfc2136.ini --dns-rfc2136-propagation-seconds 10 + +and the rfc2136.ini file looks like: + +# Target DNS server +dns_rfc2136_server = 192.168.196.248 +# Target DNS port +dns_rfc2136_port = 53 +# TSIG key name +dns_rfc2136_name = tsig-key +# TSIG key secret +dns_rfc2136_secret = 1KsWP8ZkZxBDKS0RQ2n3bkz1xpVPtz3Tk1y3r/dF+4knwGBzscse8iewaEr/6jUtxaL1taGME6eqSDtV2SD8NQ== +# TSIG key algorithm +dns_rfc2136_algorithm = HMAC-SHA512 + diff --git a/hbd b/hbd index de90537..fc67997 100755 --- a/hbd +++ b/hbd @@ -2,11 +2,8 @@ # $Id: hbd,v 1.38 2013/07/14 02:25:05 andreas Exp $ # Wait for heartbeat messages and act on them (or their absence) # -VER = 4.2 - import time import os -import string import sys import socket import ssl @@ -20,7 +17,8 @@ import signal import pickle import smtplib import traceback -import urllib.request, urllib.parse, urllib.error +import urllib.request +import urllib.error import urllib.parse import http.client import threading @@ -38,7 +36,10 @@ from subprocess import Popen, STDOUT, PIPE # from hbdclass import * import hbdclass +VER = 4.4 + CERT_PATH = "/usr/local/etc/letsencrypt/live/w02.wrede.ca/" +CERT_PATH = "./test/" WSS_PEM = CERT_PATH + "fullchain.pem" WSS_KEY = CERT_PATH + "privkey.pem" @@ -82,18 +83,18 @@ lastfm = ["", "", ""] tcss = """ """ @@ -142,7 +143,7 @@ class LogDevice: def dicttos(ID, d, compress=False): s = [] for k in d: - if type(d[k]) == type(1.2): + if isinstance(d[k], float): s.append("%s=%0.5f" % (k, d[k])) else: s.append("%s=%s" % (k, d[k])) @@ -207,8 +208,9 @@ def email(s, msg): except smtplib.SMTPRecipientsRefused as errs: log(None, "cannot send email: %s\n" % (errs)) ret = "Fail" - except: - print(("smtp error: " + traceback.format_exc())) + except Exception as e: + print(f"smtp error: {e}") + ret = "Fail" saveandrestart() try: server.quit() @@ -294,7 +296,7 @@ def pushsignal(msg, title="hbd", recipient=RECIPIENT): "send", "-m", message, - # "-g", GROUP, + # "-g", GROUP, recipient, ] @@ -313,7 +315,7 @@ def pushsignal(msg, title="hbd", recipient=RECIPIENT): # nsupdate: set the DNS A record for a fqdn -# return: None if ok, else error text +# return: None if ok, else error text def nsupdate(hostname, newip, dyndomain): D = {} D["domain"] = dyndomain @@ -424,8 +426,7 @@ def checkoverdue(): conn.newstate(hbdclass.Connection.overdue, now, grace) pmsg.append(conn.afam) if ( - conn.state == hbdclass.Connection.overdue - and (now - conn.lastbeat) > DROPOVERDUE + conn.state == hbdclass.Connection.overdue and (now - conn.lastbeat) > DROPOVERDUE ): conn.newstate(hbdclass.Connection.unknown, conn.lastbeat) if pmsg != []: @@ -505,7 +506,7 @@ def readsock(sock): addr = addrp[0:2] name = shortname(msg.get("name", "unknown")) - if not name in hbdclass.Host.hosts: # was: hosts.has_key(name): + if name not in hbdclass.Host.hosts: # was: hosts.has_key(name): host = hbdclass.Host(name) host.dyn = name in dyndnshosts if verbose: @@ -550,7 +551,7 @@ def readsock(sock): email("msg", message) pushmsg(message) - if conn.getstate() != hbdclass.Connection.up: # XXX and interval > 0: + if conn.getstate() != hbdclass.Connection.up: # XXX and interval > 0: lasts = conn.state d = conn.newstate(hbdclass.Connection.up, now) m = "%s back after being %s for %s" % (conn.afam, lasts, dur(d)) @@ -661,7 +662,7 @@ class HttpHandler(http.server.BaseHTTPRequestHandler): return self.server_version def handle(self): - # return http.server.BaseHTTPRequestHandler.handle(self) + # return http.server.BaseHTTPRequestHandler.handle(self) try: return http.server.BaseHTTPRequestHandler.handle(self) except Exception as e: @@ -678,8 +679,8 @@ class HttpHandler(http.server.BaseHTTPRequestHandler): "Last-Modified", time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now)), ) - # self.send_header("Accept-Ranges","bytes") - # self.send_header("hbdclass.Connection","close") + # self.send_header("Accept-Ranges","bytes") + # self.send_header("hbdclass.Connection","close") for h in headerdict: self.send_header(h, headerdict[h]) self.end_headers() @@ -764,11 +765,11 @@ class HttpHandler(http.server.BaseHTTPRequestHandler): uname = qa.get("h", [None])[0] if not uname: code, res = self.builderror(400, "Argument error", "need h= argument") - if not uname in hbdclass.Host.hosts: + if uname not in hbdclass.Host.hosts: code, res = self.builderror(400, "Data error", "h=%s not found" % uname) else: log(uname, "dropped") - # for addr in hbdclass.Host.hosts[uname].0i + # for addr in hbdclass.Host.hosts[uname].0i # TODO: send message to websocket about dropped host del hbdclass.Host.hosts[uname] res = self.buildhead() @@ -778,7 +779,7 @@ class HttpHandler(http.server.BaseHTTPRequestHandler): uname = qa.get("h", [None])[0] if not uname: code, res = self.builderror(400, "Argument error", "need h= argument") - if not uname in hbdclass.Host.hosts: + if uname not in hbdclass.Host.hosts: code, res = self.builderror(400, "Data error", "h=%s not found" % uname) else: ll = hbdclass.Host.hosts[uname].registerDns() @@ -814,15 +815,15 @@ class HttpHandler(http.server.BaseHTTPRequestHandler): elif qr.path == "/api/0/hosts": # api access to host table headerdict = {"Content-Type": "application/json; charset=utf-8"} - l = [] + lst = [] for h in hbdclass.Host.hosts: - l.append(hbdclass.Host.hosts[h].jsons()) - res = ["[" + ",".join(l) + "]"] + lst.append(hbdclass.Host.hosts[h].jsons()) + res = ["[" + ",".join(lst) + "]"] elif qr.path == "/api/0/messages": # api access to host table headerdict = {"Content-Type": "application/json; charset=utf-8"} - l = msgs[len(msgs) - 30 :] - res = [json.dumps(l)] + lst = msgs[len(msgs) - 30:] + res = [json.dumps(lst)] elif qr.path == "/r": # restart res = self.buildhead() @@ -889,7 +890,7 @@ def closeup(): except: pass - # signal.signal(signal.SIGTERM, 0) + # signal.signal(signal.SIGTERM, 0) signal.signal(signal.SIGHUP, 0) @@ -947,7 +948,7 @@ async def ws_serve(websocket, path): ) await websocket.send(jmsg) # messages in reverse order - for m in msgs[len(msgs) - 20 :]: + for m in msgs[len(msgs) - 20:]: jmsg = json.dumps({"type": "message", "data": m}) await websocket.send(jmsg) @@ -1076,17 +1077,17 @@ if f: ls = f.readline() if len(ls) == 0: break - l = ls[:-1].strip() - if len(l) == 0 or l[0] == "#": + ln = ls[:-1].strip() + if len(ln) == 0 or ln[0] == "#": continue if verbose: - print((" %s" % l)) - r = l.split("=") + print((" %s" % ln)) + r = ln.split("=") o = r[0].strip() try: a = eval(r[1].strip()) except Exception as e: - print(("error: %s" % str(r))) + print("error: %s %s" % (e, str(r))) sys.exit(1) if o == "interval": interval = a @@ -1222,7 +1223,11 @@ asyncio.set_event_loop(loop) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) wss_pem = pathlib.Path(WSS_PEM) wss_key = pathlib.Path(WSS_KEY) -ssl_context.load_cert_chain(wss_pem, keyfile=wss_key) +try: + ssl_context.load_cert_chain(wss_pem, keyfile=wss_key) +except FileNotFoundError: + print(("warning: missing %s or %s" % (wss_pem, wss_key))) + sys.exit(1) wss_start_server = websockets.serve( ws_serve, hbd_host, WSSPORT, ssl=ssl_context, loop=loop, subprotocols=["hbd"] ) @@ -1281,8 +1286,8 @@ while running: for fh in sr[0]: if fh in [sock, sock6]: readsock(fh) - # elif fh == serv.fileno(): - # serv.handle_request() + # elif fh == serv.fileno(): + # serv.handle_request() else: sys.stderr.write("what happend just now?\n") if DEBUG > 3: diff --git a/hbdclass.py b/hbdclass.py index d99bad6..6a9501a 100644 --- a/hbdclass.py +++ b/hbdclass.py @@ -1,6 +1,6 @@ """ -host and connection class shared between hbd and -the websit's heartbeat.py +host and connection class shared between hbd and +the websit's heartbeat.py """ @@ -234,7 +234,7 @@ class Host: return self.dyn def isIPv4(self, addr): - if type(addr) == type(()): + if isinstance(addr, tuple): return addr[0].find(".") > 0 else: return addr.find(".") > 0 @@ -328,7 +328,7 @@ class Host: hostfields = Host.hostfields_long h = [] for f in hostfields: - if type(f) == type(()): + if isinstance(f, tuple): h.append(self.gene(tag, hd[f[0]], f[1])) else: h.append(self.gene(tag, hd[f])) @@ -354,7 +354,7 @@ class Host: res = [] le = max(40 - len(Host.hosts), 3) res.append("

Log of Events

") - for m in msgs[len(msgs) - le :]: + for m in msgs[len(msgs) - le:]: res.append("%s
" % m) return res diff --git a/sorttable.js b/sorttable.js new file mode 100644 index 0000000..38b0fc6 --- /dev/null +++ b/sorttable.js @@ -0,0 +1,495 @@ +/* + SortTable + version 2 + 7th April 2007 + Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ + + Instructions: + Download this file + Add to your HTML + Add class="sortable" to any table you'd like to make sortable + Click on the headers to sort + + Thanks to many, many people for contributions and suggestions. + Licenced as X11: http://www.kryogenix.org/code/browser/licence.html + This basically means: do what you want with it. +*/ + + +var stIsIE = /*@cc_on!@*/false; + +sorttable = { + init: function() { + // quit if this function has already been called + if (arguments.callee.done) return; + // flag this function so we don't do the same thing twice + arguments.callee.done = true; + // kill the timer + if (_timer) clearInterval(_timer); + + if (!document.createElement || !document.getElementsByTagName) return; + + sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; + + forEach(document.getElementsByTagName('table'), function(table) { + if (table.className.search(/\bsortable\b/) != -1) { + sorttable.makeSortable(table); + } + }); + + }, + + makeSortable: function(table) { + if (table.getElementsByTagName('thead').length == 0) { + // table doesn't have a tHead. Since it should have, create one and + // put the first table row in it. + the = document.createElement('thead'); + the.appendChild(table.rows[0]); + table.insertBefore(the,table.firstChild); + } + // Safari doesn't support table.tHead, sigh + if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; + + if (table.tHead.rows.length != 1) return; // can't cope with two header rows + + // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as + // "total" rows, for example). This is B&R, since what you're supposed + // to do is put them in a tfoot. So, if there are sortbottom rows, + // for backwards compatibility, move them to tfoot (creating it if needed). + sortbottomrows = []; + for (var i=0; i5' : ' ▴'; + this.appendChild(sortrevind); + return; + } + if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { + // if we're already sorted by this column in reverse, just + // re-reverse the table, which is quicker + sorttable.reverse(this.sorttable_tbody); + this.className = this.className.replace('sorttable_sorted_reverse', + 'sorttable_sorted'); + this.removeChild(document.getElementById('sorttable_sortrevind')); + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + return; + } + + // remove sorttable_sorted classes + theadrow = this.parentNode; + forEach(theadrow.childNodes, function(cell) { + if (cell.nodeType == 1) { // an element + cell.className = cell.className.replace('sorttable_sorted_reverse',''); + cell.className = cell.className.replace('sorttable_sorted',''); + } + }); + sortfwdind = document.getElementById('sorttable_sortfwdind'); + if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } + sortrevind = document.getElementById('sorttable_sortrevind'); + if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } + + this.className += ' sorttable_sorted'; + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + + // build an array to sort. This is a Schwartzian transform thing, + // i.e., we "decorate" each row with the actual sort key, + // sort based on the sort keys, and then put the rows back in order + // which is a lot faster because you only do getInnerText once per row + row_array = []; + col = this.sorttable_columnindex; + rows = this.sorttable_tbody.rows; + for (var j=0; j 12) { + // definitely dd/mm + return sorttable.sort_ddmm; + } else if (second > 12) { + return sorttable.sort_mmdd; + } else { + // looks like a date, but we can't tell which, so assume + // that it's dd/mm (English imperialism!) and keep looking + sortfn = sorttable.sort_ddmm; + } + } + } + } + return sortfn; + }, + + getInnerText: function(node) { + // gets the text we want to use for sorting for a cell. + // strips leading and trailing whitespace. + // this is *not* a generic getInnerText function; it's special to sorttable. + // for example, you can override the cell text with a customkey attribute. + // it also gets .value for fields. + + if (!node) return ""; + + hasInputs = (typeof node.getElementsByTagName == 'function') && + node.getElementsByTagName('input').length; + + if (node.getAttribute("sorttable_customkey") != null) { + return node.getAttribute("sorttable_customkey"); + } + else if (typeof node.textContent != 'undefined' && !hasInputs) { + return node.textContent.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.innerText != 'undefined' && !hasInputs) { + return node.innerText.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.text != 'undefined' && !hasInputs) { + return node.text.replace(/^\s+|\s+$/g, ''); + } + else { + switch (node.nodeType) { + case 3: + if (node.nodeName.toLowerCase() == 'input') { + return node.value.replace(/^\s+|\s+$/g, ''); + } + case 4: + return node.nodeValue.replace(/^\s+|\s+$/g, ''); + break; + case 1: + case 11: + var innerText = ''; + for (var i = 0; i < node.childNodes.length; i++) { + innerText += sorttable.getInnerText(node.childNodes[i]); + } + return innerText.replace(/^\s+|\s+$/g, ''); + break; + default: + return ''; + } + } + }, + + reverse: function(tbody) { + // reverse the rows in a tbody + newrows = []; + for (var i=0; i=0; i--) { + tbody.appendChild(newrows[i]); + } + delete newrows; + }, + + /* sort functions + each sort function takes two parameters, a and b + you are comparing a[0] and b[0] */ + sort_numeric: function(a,b) { + aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); + if (isNaN(aa)) aa = 0; + bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); + if (isNaN(bb)) bb = 0; + return aa-bb; + }, + sort_alpha: function(a,b) { + if (a[0]==b[0]) return 0; + if (a[0] 0 ) { + var q = list[i]; list[i] = list[i+1]; list[i+1] = q; + swap = true; + } + } // for + t--; + + if (!swap) break; + + for(var i = t; i > b; --i) { + if ( comp_func(list[i], list[i-1]) < 0 ) { + var q = list[i]; list[i] = list[i-1]; list[i-1] = q; + swap = true; + } + } // for + b++; + + } // while(swap) + } +} + +/* ****************************************************************** + Supporting functions: bundled here to avoid depending on a library + ****************************************************************** */ + +// Dean Edwards/Matthias Miller/John Resig + +/* for Mozilla/Opera9 */ +if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", sorttable.init, false); +} + +/* for Internet Explorer */ +/*@cc_on @*/ +/*@if (@_win32) + document.write("