Implement XMPP connection request

This commit is contained in:
Zaid Abdulla committed 2020-12-08 02:08:25 -08:00
1 parent 82ed742a67
commit 79ede00ef2
5 files changed
+170 -17

No files matched your search

+37 -12
View File
@@ -29,10 +29,14 @@ import {
import {
httpConnectionRequest,
udpConnectionRequest,
xmppConnectionRequest,
} from "./connection-request";
import { Expression, Task } from "./types";
import { flattenDevice } from "./mongodb-functions";
import { evaluate } from "./common/expression";
import * as config from "../lib/config";
const XMPP_CONFIGURED = !!config.get("XMPP_JID");
export async function connectionRequest(
deviceId: string,
@@ -44,7 +48,11 @@ export async function connectionRequest(
device = flattenDevice(res);
}
let connectionRequestUrl, udpConnectionRequestAddress, username, password;
let connectionRequestUrl,
udpConnectionRequestAddress,
connReqJabberId,
username,
password;
if (device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]) {
connectionRequestUrl = (device[
@@ -55,6 +63,9 @@ export async function connectionRequest(
"InternetGatewayDevice.ManagementServer.UDPConnectionRequestAddress"
] || {}
).value || [""])[0];
connReqJabberId = ((
device["InternetGatewayDevice.ManagementServer.ConnReqJabberID"] || {}
).value || [""])[0];
username = ((
device[
"InternetGatewayDevice.ManagementServer.ConnectionRequestUsername"
@@ -72,6 +83,8 @@ export async function connectionRequest(
udpConnectionRequestAddress = ((
device["Device.ManagementServer.UDPConnectionRequestAddress"] || {}
).value || [""])[0];
connReqJabberId = ((device["Device.ManagementServer.ConnReqJabberID"] || {})
.value || [""])[0];
username = ((
device["Device.ManagementServer.ConnectionRequestUsername"] || {}
).value || [""])[0];
@@ -156,18 +169,30 @@ export async function connectionRequest(
);
}
const status = await httpConnectionRequest(
connectionRequestUrl,
authExp,
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
CONNECTION_REQUEST_TIMEOUT,
debug,
deviceId
);
let status;
if (udpProm) {
await udpProm;
return "";
if (connReqJabberId && XMPP_CONFIGURED) {
status = await xmppConnectionRequest(
connReqJabberId,
authExp,
CONNECTION_REQUEST_TIMEOUT,
debug,
deviceId
);
} else {
status = await httpConnectionRequest(
connectionRequestUrl,
authExp,
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
CONNECTION_REQUEST_TIMEOUT,
debug,
deviceId
);
if (udpProm) {
await udpProm;
return "";
}
}
return status;
+3
View File
@@ -101,6 +101,9 @@ const options = {
// Should probably never be changed
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 },
XMPP_JID: { type: "string", default: "" },
XMPP_PASSWORD: { type: "string", default: "" },
};
const allConfig: { [name: string]: string | number } = {};
+88
View File
@@ -26,6 +26,10 @@ import { Expression } from "./types";
import * as auth from "./auth";
import * as extensions from "./extensions";
import * as debug from "./debug";
import XmppClient from "./xmpp-client";
import * as config from "../lib/config";
import { encodeEntities, parseAttrs, Element } from "./xml-parser";
import * as logger from "../lib/logger";
async function extractAuth(
exp: Expression,
@@ -234,3 +238,87 @@ export async function udpConnectionRequest(
}
client.close();
}
const XMPP_JID = config.get("XMPP_JID") as string;
const XMPP_PASSWORD = config.get("XMPP_PASSWORD") as string;
const XMPP_RESOURCE = crypto.randomBytes(8).toString("hex");
let xmppClient: XmppClient;
let xmppclientTimeout: NodeJS.Timeout;
function xmppClientOnError(err: Error): void {
clearTimeout(xmppclientTimeout);
xmppClient.disconnect();
xmppClient = null;
logger.error({
message: "XMPP exception",
exception: err,
pid: process.pid,
});
}
function xmppClientTimeoutCallback(): void {
xmppClient.disconnect();
xmppClient = null;
}
export async function xmppConnectionRequest(
jid: string,
authExp: Expression,
timeout: number,
_debug: boolean,
deviceId: string
): Promise<string> {
if (!xmppClient) {
const [host, username] = XMPP_JID.split("@").reverse();
xmppClient = await XmppClient.connect({
host,
username,
resource: XMPP_RESOURCE,
password: XMPP_PASSWORD,
});
xmppClient.on("error", xmppClientOnError);
}
clearTimeout(xmppclientTimeout);
setTimeout(xmppClientTimeoutCallback, 120000);
let username: string;
let password: string;
[username, password, authExp] = await extractAuth(authExp, null);
while (username != null && password != null) {
const msg = `<connectionRequest xmlns="urn:broadband-forum-org:cwmp:xmppConnReq-1-0"><username>${encodeEntities(
username
)}</username><password>${encodeEntities(
password
)}</password></connectionRequest>`;
let res: Element, rawRes: string, rawReq: string;
try {
({ res, rawRes, rawReq } = await xmppClient.sendIqStanza(
XMPP_JID,
jid,
"get",
msg,
timeout
));
} catch (err) {
return err.message;
}
if (_debug) {
debug.outgoingXmppStanza(deviceId, rawReq);
debug.incomingXmppStanza(deviceId, rawRes);
}
const attrs = parseAttrs(res.attrs);
const type = attrs.find((a) => a.name === "type");
if (type && type.value === "result") return "";
const error = res.children.find((c) => c.name === "error");
if (!error || !error.children[0])
return "Unexpected XMPP connection request response";
if (error.children[0].name === "service-unavailable")
return "Device is offline";
if (error.children[0].name !== "not-authorized")
return "Unexpected XMPP connection request response";
[username, password, authExp] = await extractAuth(authExp, null);
}
return "Incorrect connection request credentials";
}
+34
View File
@@ -202,3 +202,37 @@ export function outgoingUdpMessage(
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
}
export function outgoingXmppStanza(deviceId: string, body: string): void {
if (!DEBUG_FILE) return;
const now = new Date();
const msg = {
event: "outgoing XMPP stanza",
timestamp: now,
deviceId: deviceId,
body: body,
};
if (DEBUG_FORMAT === "yaml")
appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg));
else if (DEBUG_FORMAT === "json")
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
}
export function incomingXmppStanza(deviceId: string, body: string): void {
if (!DEBUG_FILE) return;
const now = new Date();
const msg = {
event: "incoming XMPP stanza",
timestamp: now,
deviceId: deviceId,
body: body,
};
if (DEBUG_FORMAT === "yaml")
appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg));
else if (DEBUG_FORMAT === "json")
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
}
+8 -5
View File
@@ -295,14 +295,14 @@ interface XmppClientOptions {
resource?: string;
}
export class XmppClient extends EventEmitter {
export default class XmppClient extends EventEmitter {
private _socket: net.Socket;
private _host: string;
private _username: string;
private _resource: string;
private _iqStanzaCallbacks: Map<
string,
(r: { rawRes: string; res: Element }) => void
(err: Error, r?: { rawRes: string; res: Element }) => void
>;
private constructor() {
@@ -384,7 +384,7 @@ export class XmppClient extends EventEmitter {
const id = attrs.find((a) => a.name === "id");
if (id) {
const cb = this._iqStanzaCallbacks.get(id.value);
if (cb) cb({ rawRes: s, res: c });
if (cb) cb(null, { rawRes: s, res: c });
}
}
this.emit("stanza", c, s);
@@ -397,6 +397,7 @@ export class XmppClient extends EventEmitter {
private _onError(err: Error): void {
this._socket.end();
this.emit("error", err);
for (const cb of this._iqStanzaCallbacks.values()) cb(err);
}
send(msg: string): void {
@@ -420,9 +421,11 @@ export class XmppClient extends EventEmitter {
new Error("Did not receive IQ stanza response in a timely manner")
);
}, timeout);
this._iqStanzaCallbacks.set(id, (r) => {
this._iqStanzaCallbacks.set(id, (err, r) => {
this._iqStanzaCallbacks.delete(id);
clearTimeout(t);
resolve(Object.assign(r, { rawReq }));
if (err) reject(err);
else resolve(Object.assign(r, { rawReq }));
});
});
}