Implement XMPP connection request
This commit is contained in:
1 parent
82ed742a67
commit
79ede00ef2
5 files changed
+160
-7
No files matched your search
+27
-2
@@ -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,7 +169,18 @@ export async function connectionRequest(
|
||||
);
|
||||
}
|
||||
|
||||
const status = await httpConnectionRequest(
|
||||
let status;
|
||||
|
||||
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,
|
||||
@@ -169,6 +193,7 @@ export async function connectionRequest(
|
||||
await udpProm;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -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 } = {};
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
@@ -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 }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user