Skip to content

Instantly share code, notes, and snippets.

@mattiarossi
Last active June 21, 2026 20:17
Show Gist options
  • Select an option

  • Save mattiarossi/bb14462a520b6909580fc5b477c6a37f to your computer and use it in GitHub Desktop.

Select an option

Save mattiarossi/bb14462a520b6909580fc5b477c6a37f to your computer and use it in GitHub Desktop.
NODE-RED Flow: Sercomm FG1000R Web interface to MQTT server scraper
[
{
"id": "ont_flow_tab",
"type": "tab",
"label": "ONT β†’ MQTT",
"disabled": false,
"info": "Sercomm/Realtek FG1000R GPON ONT (Boa) β†’ MQTT. Credentials live in flow context (Config node). Use the Credential Generator group to compute them from plaintext."
},
{
"id": "comment_readme",
"type": "comment",
"z": "ont_flow_tab",
"name": "ℹ️ READ ME β€” setup (open node for details)",
"info": "# ONT β†’ MQTT bridge (Sercomm/Realtek FG1000R)\n\nLogs in to the Boa web UI (IP-based session, scraped csrftoken), fetches status_pon.asp + status.asp, publishes one MQTT topic per metric.\n\n## Credentials\nStored in FLOW CONTEXT: `ont_url`, `ont_user`, `ont_pass`, `ont_flag`.\n- `ont_user`/`ont_pass` = URL-encoded MD5-crypt($1$, empty salt) hashes (NOT plaintext).\n- `ont_flag` = postSecurityFlag checksum (depends on the hashes).\n\nThe **Config** node (top) seeds these on every deploy/start β€” it is the persistent source of truth. The **πŸ”‘ Credential Generator** group computes the three hashed params from plaintext and (a) prints them and (b) live-applies them for immediate testing. To PERSIST new credentials, copy the printed `ont_user`/`ont_pass`/`ont_flag` into the Config node and Deploy.\n\n## Setup\n1. Set the **LAN broker** node host/port + Security (username/password).\n2. Either edit the Config node directly, or run the Credential Generator (enter plaintext in its inject, press it).\n3. Deploy.\n\n## Requirement\nNode-RED must reach the ONT directly (same source IP each poll β€” auth is IP-bound)."
},
{
"id": "config_inject",
"type": "inject",
"z": "ont_flow_tab",
"name": "Config (run once on deploy)",
"props": [],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "0.1",
"topic": "",
"x": 160,
"y": 100,
"wires": [["set_config"]]
},
{
"id": "set_config",
"type": "change",
"z": "ont_flow_tab",
"name": "Set flow vars (persistent creds)",
"rules": [
{ "t": "set", "p": "ont_url", "pt": "flow", "to": "http://192.168.1.1", "tot": "str" },
{ "t": "set", "p": "ont_user", "pt": "flow", "to": "%241%24%24.D8nxKYhZGdpkRzTXsgTE%2F", "tot": "str" },
{ "t": "set", "p": "ont_pass", "pt": "flow", "to": "password", "tot": "str" },
{ "t": "set", "p": "ont_flag", "pt": "flow", "to": "61734", "tot": "str" }
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 460,
"y": 100,
"wires": [[]]
},
{
"id": "inject_trigger",
"type": "inject",
"z": "ont_flow_tab",
"name": "Poll every 60s",
"props": [],
"repeat": "60",
"crontab": "",
"once": true,
"onceDelay": "2",
"topic": "",
"x": 140,
"y": 180,
"wires": [["build_login_get"]]
},
{
"id": "build_login_get",
"type": "function",
"z": "ont_flow_tab",
"name": "Build login-page GET",
"func": "const ONT = flow.get(\"ont_url\") || \"http://192.168.1.1\";\nmsg.method = \"GET\";\nmsg.url = ONT + \"/admin/login.asp\";\nmsg.headers = {};\nmsg.payload = \"\";\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 340,
"y": 180,
"wires": [["get_login_page"]]
},
{
"id": "get_login_page",
"type": "http request",
"z": "ont_flow_tab",
"name": "GET login.asp",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": {},
"x": 540,
"y": 180,
"wires": [["build_login_post"]]
},
{
"id": "build_login_post",
"type": "function",
"z": "ont_flow_tab",
"name": "Scrape csrftoken + build login POST",
"func": "const ONT = flow.get(\"ont_url\") || \"http://192.168.1.1\";\nconst USER = flow.get(\"ont_user\") || \"\";\nconst PASS = flow.get(\"ont_pass\") || \"\";\nconst FLAG = flow.get(\"ont_flag\") || \"61734\";\n\nif (!USER || !PASS || PASS === \"password\") {\n node.error(\"Set ont_user/ont_pass (run the Credential Generator or edit the Config node)\", msg);\n return null;\n}\n\nconst t = (msg.payload || \"\").match(/name=['\\\"]csrftoken['\\\"][^>]*value=['\\\"]([0-9a-f]+)['\\\"]/i);\nif (!t) { node.error(\"csrftoken not found on login page\", msg); return null; }\n\nmsg.method = \"POST\";\nmsg.url = ONT + \"/boaform/admin/formLogin\";\nmsg.headers = {\n \"content-type\": \"application/x-www-form-urlencoded\",\n \"Referer\": ONT + \"/admin/login.asp\"\n};\nmsg.payload = \"save=Login&challenge=&username=\" + USER +\n \"&password=\" + PASS +\n \"&postSecurityFlag=\" + FLAG +\n \"&csrftoken=\" + t[1];\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 820,
"y": 180,
"wires": [["post_login"]]
},
{
"id": "post_login",
"type": "http request",
"z": "ont_flow_tab",
"name": "POST formLogin (authorizes source IP)",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": true,
"headers": {},
"x": 200,
"y": 280,
"wires": [["build_pon_fetch", "build_dev_fetch"]]
},
{
"id": "build_pon_fetch",
"type": "function",
"z": "ont_flow_tab",
"name": "Build PON GET",
"func": "const ONT = flow.get(\"ont_url\") || \"http://192.168.1.1\";\nmsg.method = \"GET\";\nmsg.url = ONT + \"/admin/status_pon.asp?v=\" + Date.now();\nmsg.headers = { \"Referer\": ONT + \"/admin/login.asp\" };\nmsg.payload = \"\";\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 470,
"y": 280,
"wires": [["get_pon"]]
},
{
"id": "get_pon",
"type": "http request",
"z": "ont_flow_tab",
"name": "GET status_pon.asp",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": true,
"headers": {},
"x": 700,
"y": 280,
"wires": [["parse_pon"]]
},
{
"id": "parse_pon",
"type": "function",
"z": "ont_flow_tab",
"name": "Parse PON table (+ health)",
"func": "let body = msg.payload || \"\";\nif (typeof body !== \"string\") body = String(body);\nbody = body.replace(/<!--[\\s\\S]*?-->/g, \"\");\n\nconst raw = {};\nconst re = /<th[^>]*>([\\s\\S]*?)<\\/th>\\s*<td[^>]*>([\\s\\S]*?)<\\/td>/gi;\nlet m;\nwhile ((m = re.exec(body)) !== null) {\n const label = m[1].replace(/<[^>]*>/g, \"\").replace(/\\s+/g, \" \").trim().toLowerCase();\n const value = m[2].replace(/<[^>]*>/g, \"\").replace(/\\s+/g, \" \").trim();\n if (label) raw[label] = value;\n}\n\nfunction num(v) {\n if (v === undefined) return null;\n if (/(-?\\s*inf|nan|n\\/?a)/i.test(v)) return null;\n const mm = v.match(/-?\\d+(?:\\.\\d+)?/);\n return mm ? parseFloat(mm[0]) : null;\n}\n\nconst state = raw[\"onu state\"] || null;\nconst STATES = { O1:\"Initial\", O2:\"Standby\", O3:\"SerialNumber\", O4:\"Ranging\", O5:\"Operational\", O6:\"PopUp\", O7:\"EmergencyStop\" };\n\nconst rx = num(raw[\"rx power\"]);\nconst online = state === \"O5\";\n\nconst RX_MIN = -28, RX_MAX = -8;\nlet healthy = true;\nconst reasons = [];\nif (!online) { healthy = false; reasons.push(\"onu_state=\" + (state || \"unknown\")); }\nif (rx === null) { healthy = false; reasons.push(\"no_rx\"); }\nelse {\n if (rx < RX_MIN) { healthy = false; reasons.push(\"rx_low(\" + rx + \")\"); }\n if (rx > RX_MAX) { healthy = false; reasons.push(\"rx_high(\" + rx + \")\"); }\n}\n\nconst metrics = {\n temperature_c: num(raw[\"temperature\"]),\n voltage_v: num(raw[\"voltage\"]),\n tx_power_dbm: num(raw[\"tx power\"]),\n rx_power_dbm: rx,\n bias_current_ma: num(raw[\"bias current\"]),\n onu_state: state,\n onu_state_desc: state ? (STATES[state] || \"Unknown\") : null,\n online: online,\n healthy: healthy,\n health_reason: healthy ? \"ok\" : reasons.join(\",\"),\n ts: Math.floor(Date.now() / 1000)\n};\n\nif (metrics.temperature_c === null && metrics.onu_state === null) {\n node.warn(\"PON parse empty β€” got: \" + body.slice(0, 200));\n return null;\n}\nmsg.section = \"pon\";\nmsg.metrics = metrics;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 700,
"y": 340,
"wires": [["fanout", "debug_parsed"]]
},
{
"id": "build_dev_fetch",
"type": "function",
"z": "ont_flow_tab",
"name": "Build Device GET",
"func": "const ONT = flow.get(\"ont_url\") || \"http://192.168.1.1\";\nmsg.method = \"GET\";\nmsg.url = ONT + \"/admin/status.asp?v=\" + Date.now();\nmsg.headers = { \"Referer\": ONT + \"/admin/login.asp\" };\nmsg.payload = \"\";\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 470,
"y": 440,
"wires": [["get_dev"]]
},
{
"id": "get_dev",
"type": "http request",
"z": "ont_flow_tab",
"name": "GET status.asp",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": true,
"headers": {},
"x": 690,
"y": 440,
"wires": [["parse_dev"]]
},
{
"id": "parse_dev",
"type": "function",
"z": "ont_flow_tab",
"name": "Parse Device table (+ uptime_seconds)",
"func": "let body = msg.payload || \"\";\nif (typeof body !== \"string\") body = String(body);\nbody = body.replace(/<!--[\\s\\S]*?-->/g, \"\");\n\nconst out = {};\nconst re = /<th[^>]*>([\\s\\S]*?)<\\/th>\\s*<td[^>]*>([\\s\\S]*?)<\\/td>/gi;\nlet m;\nwhile ((m = re.exec(body)) !== null) {\n const label = m[1].replace(/<[^>]*>/g, \"\").replace(/\\s+/g, \" \").trim();\n const value = m[2].replace(/<[^>]*>/g, \"\").replace(/\\s+/g, \" \").trim();\n if (!label || value === \"\") continue;\n const key = label.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\");\n const pct = value.match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n out[key] = pct ? parseFloat(pct[1]) : value;\n}\n\nconst wan = body.match(/INTERNET[\\s\\S]{0,400}?>\\s*(up|down)\\s*</i);\nif (wan) out.wan_status = wan[1].toLowerCase();\n\nfunction uptimeToSeconds(s) {\n if (!s) return null;\n s = String(s).trim();\n let days = 0;\n const dm = s.match(/(\\d+)\\s*(?:d|day)/i);\n if (dm) { days = parseInt(dm[1], 10); s = s.replace(dm[0], \"\"); }\n const parts = s.split(\":\").map(function (x) { return parseInt(x, 10); }).filter(function (x) { return !isNaN(x); });\n let h = 0, mi = 0, se = 0;\n if (parts.length === 3) { h = parts[0]; mi = parts[1]; se = parts[2]; }\n else if (parts.length === 2) { h = parts[0]; mi = parts[1]; }\n else if (parts.length === 1) { mi = parts[0]; }\n else return null;\n return ((days * 24 + h) * 60 + mi) * 60 + se;\n}\nif (out.uptime !== undefined) {\n const u = uptimeToSeconds(out.uptime);\n if (u !== null) out.uptime_seconds = u;\n}\n\nout.ts = Math.floor(Date.now() / 1000);\n\nif (Object.keys(out).length <= 1) {\n node.warn(\"Device parse empty β€” got: \" + body.slice(0, 200));\n return null;\n}\nmsg.section = \"device\";\nmsg.metrics = out;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 690,
"y": 500,
"wires": [["fanout", "debug_dev"]]
},
{
"id": "fanout",
"type": "function",
"z": "ont_flow_tab",
"name": "Fan out β†’ one topic per metric",
"func": "const root = \"network/ont/fg1000r\";\nconst section = msg.section || \"misc\";\nconst base = root + \"/\" + section;\nconst m = msg.metrics || {};\nconst outMsgs = [];\nfor (const [k, v] of Object.entries(m)) {\n if (k === \"ts\") continue;\n if (v === null || v === undefined || v === \"\") continue;\n outMsgs.push({ topic: base + \"/\" + k, payload: v, qos: 1, retain: true });\n}\noutMsgs.push({ topic: base + \"/state\", payload: m, qos: 1, retain: true });\nreturn [outMsgs];",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 990,
"y": 400,
"wires": [["mqtt_publish"]]
},
{
"id": "mqtt_publish",
"type": "mqtt out",
"z": "ont_flow_tab",
"name": "MQTT Publish",
"topic": "",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "ont_mqtt_broker",
"x": 1230,
"y": 400,
"wires": []
},
{
"id": "debug_parsed",
"type": "debug",
"z": "ont_flow_tab",
"name": "PON metrics",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
"complete": "metrics",
"targetType": "msg",
"statusVal": "metrics.onu_state",
"statusType": "msg",
"x": 980,
"y": 340,
"wires": []
},
{
"id": "debug_dev",
"type": "debug",
"z": "ont_flow_tab",
"name": "Device metrics",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
"complete": "metrics",
"targetType": "msg",
"statusVal": "metrics.uptime",
"statusType": "msg",
"x": 980,
"y": 500,
"wires": []
},
{
"id": "catch_all",
"type": "catch",
"z": "ont_flow_tab",
"name": "Catch errors",
"scope": null,
"uncaught": false,
"x": 140,
"y": 580,
"wires": [["debug_errors"]]
},
{
"id": "debug_errors",
"type": "debug",
"z": "ont_flow_tab",
"name": "Errors",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "error",
"targetType": "msg",
"x": 340,
"y": 580,
"wires": []
},
{
"id": "gen_group",
"type": "group",
"z": "ont_flow_tab",
"style": { "label": true, "label-position": "nw", "color": "#f9fbe7" },
"nodes": ["comment_gen", "gen_inject", "gen_compute", "gen_apply", "gen_debug"],
"name": "πŸ”‘ Credential Generator β€” enter plaintext in the inject, then press it",
"x": 54,
"y": 664,
"w": 1132,
"h": 182
},
{
"id": "comment_gen",
"type": "comment",
"z": "ont_flow_tab",
"g": "gen_group",
"name": "Computes ont_user / ont_pass / ont_flag from plaintext (pure JS, self-checked). Live-applies + prints them.",
"info": "Open the inject node, set `username` / `password` (and `url` if different), press its button.\n\nThe function reproduces the ONT's client-side crypto:\n- `ont_user`/`ont_pass` = MD5-crypt($1$, empty salt) of the plaintext, URL-encoded.\n- `ont_flag` = postSecurityFlag checksum, layout auto-calibrated against the known value.\n\nIt verifies its MD5-crypt against known device values before producing anything. Results are written to flow context (live) AND printed in the debug sidebar. To persist across restarts, copy the printed values into the Config node and Deploy.",
"x": 360,
"y": 700,
"wires": []
},
{
"id": "gen_inject",
"type": "inject",
"z": "ont_flow_tab",
"g": "gen_group",
"name": "Enter creds β†’ press",
"props": [
{ "p": "username", "v": "Tech", "vt": "str" },
{ "p": "password", "v": "", "vt": "str" },
{ "p": "url", "v": "http://192.168.1.1", "vt": "str" }
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 170,
"y": 760,
"wires": [["gen_compute"]]
},
{
"id": "gen_compute",
"type": "function",
"z": "ont_flow_tab",
"g": "gen_group",
"name": "Compute params (MD5-crypt + postSecurityFlag)",
"func": "const username = (msg.username || \"\").toString();\nconst password = (msg.password || \"\").toString();\nconst ontUrl = (msg.url || flow.get(\"ont_url\") || \"http://192.168.1.1\").toString();\nif (!username || !password) { node.error(\"Set username & password in the inject node fields\", msg); return null; }\n\nfunction md5bytes(bytes) {\n function sa(x,y){ var l=(x&0xFFFF)+(y&0xFFFF); var m=(x>>16)+(y>>16)+(l>>16); return (m<<16)|(l&0xFFFF); }\n function rol(n,c){ return (n<<c)|(n>>>(32-c)); }\n function cmn(q,a,b,x,s,t){ return sa(rol(sa(sa(a,q),sa(x,t)),s),b); }\n function ff(a,b,c,d,x,s,t){ return cmn((b&c)|(~b&d),a,b,x,s,t); }\n function gg(a,b,c,d,x,s,t){ return cmn((b&d)|(c&~d),a,b,x,s,t); }\n function hh(a,b,c,d,x,s,t){ return cmn(b^c^d,a,b,x,s,t); }\n function ii(a,b,c,d,x,s,t){ return cmn(c^(b|~d),a,b,x,s,t); }\n var M = bytes.slice(); var n = bytes.length; M.push(0x80);\n while (M.length % 64 !== 56) M.push(0);\n var bits = n*8; for (var k=0;k<4;k++) M.push((bits>>>(8*k))&0xff); for (k=0;k<4;k++) M.push(0);\n var a=1732584193,b=-271733879,c=-1732584194,d=271733878;\n for (var off=0; off<M.length; off+=64) {\n var x=[]; for (var j=0;j<16;j++) x[j]=M[off+j*4]|(M[off+j*4+1]<<8)|(M[off+j*4+2]<<16)|(M[off+j*4+3]<<24);\n var oa=a,ob=b,oc=c,od=d;\n a=ff(a,b,c,d,x[0],7,-680876936);d=ff(d,a,b,c,x[1],12,-389564586);c=ff(c,d,a,b,x[2],17,606105819);b=ff(b,c,d,a,x[3],22,-1044525330);\n a=ff(a,b,c,d,x[4],7,-176418897);d=ff(d,a,b,c,x[5],12,1200080426);c=ff(c,d,a,b,x[6],17,-1473231341);b=ff(b,c,d,a,x[7],22,-45705983);\n a=ff(a,b,c,d,x[8],7,1770035416);d=ff(d,a,b,c,x[9],12,-1958414417);c=ff(c,d,a,b,x[10],17,-42063);b=ff(b,c,d,a,x[11],22,-1990404162);\n a=ff(a,b,c,d,x[12],7,1804603682);d=ff(d,a,b,c,x[13],12,-40341101);c=ff(c,d,a,b,x[14],17,-1502002290);b=ff(b,c,d,a,x[15],22,1236535329);\n a=gg(a,b,c,d,x[1],5,-165796510);d=gg(d,a,b,c,x[6],9,-1069501632);c=gg(c,d,a,b,x[11],14,643717713);b=gg(b,c,d,a,x[0],20,-373897302);\n a=gg(a,b,c,d,x[5],5,-701558691);d=gg(d,a,b,c,x[10],9,38016083);c=gg(c,d,a,b,x[15],14,-660478335);b=gg(b,c,d,a,x[4],20,-405537848);\n a=gg(a,b,c,d,x[9],5,568446438);d=gg(d,a,b,c,x[14],9,-1019803690);c=gg(c,d,a,b,x[3],14,-187363961);b=gg(b,c,d,a,x[8],20,1163531501);\n a=gg(a,b,c,d,x[13],5,-1444681467);d=gg(d,a,b,c,x[2],9,-51403784);c=gg(c,d,a,b,x[7],14,1735328473);b=gg(b,c,d,a,x[12],20,-1926607734);\n a=hh(a,b,c,d,x[5],4,-378558);d=hh(d,a,b,c,x[8],11,-2022574463);c=hh(c,d,a,b,x[11],16,1839030562);b=hh(b,c,d,a,x[14],23,-35309556);\n a=hh(a,b,c,d,x[1],4,-1530992060);d=hh(d,a,b,c,x[4],11,1272893353);c=hh(c,d,a,b,x[7],16,-155497632);b=hh(b,c,d,a,x[10],23,-1094730640);\n a=hh(a,b,c,d,x[13],4,681279174);d=hh(d,a,b,c,x[0],11,-358537222);c=hh(c,d,a,b,x[3],16,-722521979);b=hh(b,c,d,a,x[6],23,76029189);\n a=hh(a,b,c,d,x[9],4,-640364487);d=hh(d,a,b,c,x[12],11,-421815835);c=hh(c,d,a,b,x[15],16,530742520);b=hh(b,c,d,a,x[2],23,-995338651);\n a=ii(a,b,c,d,x[0],6,-198630844);d=ii(d,a,b,c,x[7],10,1126891415);c=ii(c,d,a,b,x[14],15,-1416354905);b=ii(b,c,d,a,x[5],21,-57434055);\n a=ii(a,b,c,d,x[12],6,1700485571);d=ii(d,a,b,c,x[3],10,-1894986606);c=ii(c,d,a,b,x[10],15,-1051523);b=ii(b,c,d,a,x[1],21,-2054922799);\n a=ii(a,b,c,d,x[8],6,1873313359);d=ii(d,a,b,c,x[15],10,-30611744);c=ii(c,d,a,b,x[6],15,-1560198380);b=ii(b,c,d,a,x[13],21,1309151649);\n a=ii(a,b,c,d,x[4],6,-145523070);d=ii(d,a,b,c,x[11],10,-1120210379);c=ii(c,d,a,b,x[2],15,718787259);b=ii(b,c,d,a,x[9],21,-343485551);\n a=sa(a,oa);b=sa(b,ob);c=sa(c,oc);d=sa(d,od);\n }\n var out=[]; var ws=[a,b,c,d];\n for (var w=0;w<4;w++){ out.push(ws[w]&0xff,(ws[w]>>>8)&0xff,(ws[w]>>>16)&0xff,(ws[w]>>>24)&0xff); }\n return out;\n}\n\nfunction utf8(str){\n var o=[]; for (var i=0;i<str.length;i++){ var c=str.charCodeAt(i);\n if (c<0x80) o.push(c);\n else if (c<0x800) o.push(0xc0|(c>>6),0x80|(c&0x3f));\n else if (c<0xd800||c>=0xe000) o.push(0xe0|(c>>12),0x80|((c>>6)&0x3f),0x80|(c&0x3f));\n else { i++; var c2=str.charCodeAt(i); var cp=0x10000+(((c&0x3ff)<<10)|(c2&0x3ff)); o.push(0xf0|(cp>>18),0x80|((cp>>12)&0x3f),0x80|((cp>>6)&0x3f),0x80|(cp&0x3f)); }\n } return o;\n}\n\nfunction md5crypt(pwStr, saltStr){\n var MAGIC=\"$1$\";\n var ss = saltStr.indexOf(MAGIC)===0 ? MAGIC.length : 0;\n var dollar = saltStr.indexOf(\"$\", ss);\n var se = Math.min(8+MAGIC.length, dollar); if (se<0) se=8+MAGIC.length;\n var realSalt = saltStr.substring(ss, se);\n var pw=utf8(pwStr), salt=utf8(realSalt), magic=utf8(MAGIC);\n var fin = md5bytes(pw.concat(salt).concat(pw));\n var ctx = pw.concat(magic).concat(salt);\n for (var pl=pw.length; pl>0; pl-=16) ctx = ctx.concat(fin.slice(0, Math.min(pl,16)));\n for (var i=pw.length; i!==0; i>>>=1) ctx.push((i&1)?0:pw[0]);\n fin = md5bytes(ctx);\n for (i=0;i<1000;i++){ var t=[];\n t = (i&1) ? t.concat(pw) : t.concat(fin);\n if (i%3) t = t.concat(salt);\n if (i%7) t = t.concat(pw);\n t = (i&1) ? t.concat(fin) : t.concat(pw);\n fin = md5bytes(t);\n }\n var itoa=\"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n function to64(v,nn){ var r=\"\"; while(--nn>=0){ r+=itoa.charAt(v&0x3f); v>>=6; } return r; }\n return MAGIC+realSalt+\"$\"+\n to64((fin[0]<<16)|(fin[6]<<8)|fin[12],4)+to64((fin[1]<<16)|(fin[7]<<8)|fin[13],4)+\n to64((fin[2]<<16)|(fin[8]<<8)|fin[14],4)+to64((fin[3]<<16)|(fin[9]<<8)|fin[15],4)+\n to64((fin[4]<<16)|(fin[10]<<8)|fin[5],4)+to64(fin[11],2);\n}\n\nfunction encField(raw){\n var v = encodeURIComponent(raw);\n v = v.replace(/!/g,\"%21\").replace(/'/g,\"%27\").replace(/\\(/g,\"%28\").replace(/\\)/g,\"%29\").replace(/~/g,\"%7E\").replace(/%20/g,\"+\");\n return v;\n}\n\nfunction checksum(s){\n var csum=0,i=0;\n while (i<s.length){\n if ((i+4)>s.length){\n if (i<s.length) csum += (s.charCodeAt(i)<<24);\n if ((i+1)<s.length) csum += (s.charCodeAt(i+1)<<16);\n if ((i+2)<s.length) csum += (s.charCodeAt(i+2)<<8);\n break;\n } else {\n csum += (s.charCodeAt(i)<<24)+(s.charCodeAt(i+1)<<16)+(s.charCodeAt(i+2)<<8)+s.charCodeAt(i+3);\n i+=4;\n }\n }\n csum=(csum&0xffff)+(csum>>16); csum=csum&0xffff; csum=(~csum)&0xffff; return csum;\n}\n\nvar REF=[[\"Tech\",\"$1$$.D8nxKYhZGdpkRzTXsgTE/\"],[\"ftth@!\",\"$1$$ztOQkszu.3R2VlMasrsub/\"]];\nfor (var r=0;r<REF.length;r++){ var got=md5crypt(REF[r][0],\"$1$\"); if (got!==REF[r][1]){ node.error(\"MD5-crypt self-check failed: '\"+REF[r][0]+\"' -> \"+got+\" (expected \"+REF[r][1]+\"). Aborting.\", msg); return null; } }\n\nvar KU=encField(\"$1$$.D8nxKYhZGdpkRzTXsgTE/\"), KP=encField(\"$1$$ztOQkszu.3R2VlMasrsub/\"), KNOWN_FLAG=61734;\nvar STRATS=[\n function(u,p){ return \"save=Login&challenge=&username=\"+u+\"&password=\"+p+\"&\"; },\n function(u,p){ return \"challenge=&username=\"+u+\"&password=\"+p+\"&\"; },\n function(u,p){ return \"username=\"+u+\"&password=\"+p+\"&\"; },\n function(u,p){ return \"save=Login&username=\"+u+\"&password=\"+p+\"&challenge=&\"; },\n function(u,p){ return \"challenge=&username=\"+u+\"&password=\"+p+\"&save=Login&\"; },\n function(u,p){ return \"save=Login&challenge=&password=\"+p+\"&username=\"+u+\"&\"; }\n];\nvar strat=-1; for (var si=0;si<STRATS.length;si++){ if (checksum(STRATS[si](KU,KP))===KNOWN_FLAG){ strat=si; break; } }\n\nvar ont_user=encField(md5crypt(username,\"$1$\"));\nvar ont_pass=encField(md5crypt(password,\"$1$\"));\nvar ont_flag, note;\nif (strat>=0){ ont_flag=String(checksum(STRATS[strat](ont_user,ont_pass))); note=\"postSecurityFlag computed via layout #\"+strat; }\nelse { ont_flag=String(flow.get(\"ont_flag\")||\"61734\"); note=\"WARNING: postSecurityFlag layout not calibrated; kept \"+ont_flag+\" (login may 400 if password changed)\"; node.warn(note); }\n\nmsg.params = { ont_url: ontUrl, ont_user: ont_user, ont_pass: ont_pass, ont_flag: ont_flag };\nmsg.payload = { ont_url: ontUrl, ont_user: ont_user, ont_pass: ont_pass, ont_flag: ont_flag, note: note };\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 540,
"y": 760,
"wires": [["gen_apply", "gen_debug"]]
},
{
"id": "gen_apply",
"type": "change",
"z": "ont_flow_tab",
"g": "gen_group",
"name": "Apply β†’ flow context (live)",
"rules": [
{ "t": "set", "p": "ont_url", "pt": "flow", "to": "params.ont_url", "tot": "msg" },
{ "t": "set", "p": "ont_user", "pt": "flow", "to": "params.ont_user", "tot": "msg" },
{ "t": "set", "p": "ont_pass", "pt": "flow", "to": "params.ont_pass", "tot": "msg" },
{ "t": "set", "p": "ont_flag", "pt": "flow", "to": "params.ont_flag", "tot": "msg" }
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 850,
"y": 760,
"wires": [[]]
},
{
"id": "gen_debug",
"type": "debug",
"z": "ont_flow_tab",
"g": "gen_group",
"name": "Generated params",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"x": 1060,
"y": 760,
"wires": []
},
{
"id": "ont_mqtt_broker",
"type": "mqtt-broker",
"name": "LAN broker",
"broker": "192.168.1.10",
"port": "1883",
"clientid": "nodered-ont",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment