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 {
|
import {
|
||||||
httpConnectionRequest,
|
httpConnectionRequest,
|
||||||
udpConnectionRequest,
|
udpConnectionRequest,
|
||||||
|
xmppConnectionRequest,
|
||||||
} from "./connection-request";
|
} from "./connection-request";
|
||||||
import { Expression, Task } from "./types";
|
import { Expression, Task } from "./types";
|
||||||
import { flattenDevice } from "./mongodb-functions";
|
import { flattenDevice } from "./mongodb-functions";
|
||||||
import { evaluate } from "./common/expression";
|
import { evaluate } from "./common/expression";
|
||||||
|
import * as config from "../lib/config";
|
||||||
|
|
||||||
|
const XMPP_CONFIGURED = !!config.get("XMPP_JID");
|
||||||
|
|
||||||
export async function connectionRequest(
|
export async function connectionRequest(
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
@@ -44,7 +48,11 @@ export async function connectionRequest(
|
|||||||
device = flattenDevice(res);
|
device = flattenDevice(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let connectionRequestUrl, udpConnectionRequestAddress, username, password;
|
let connectionRequestUrl,
|
||||||
|
udpConnectionRequestAddress,
|
||||||
|
connReqJabberId,
|
||||||
|
username,
|
||||||
|
password;
|
||||||
|
|
||||||
if (device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]) {
|
if (device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]) {
|
||||||
connectionRequestUrl = (device[
|
connectionRequestUrl = (device[
|
||||||
@@ -55,6 +63,9 @@ export async function connectionRequest(
|
|||||||
"InternetGatewayDevice.ManagementServer.UDPConnectionRequestAddress"
|
"InternetGatewayDevice.ManagementServer.UDPConnectionRequestAddress"
|
||||||
] || {}
|
] || {}
|
||||||
).value || [""])[0];
|
).value || [""])[0];
|
||||||
|
connReqJabberId = ((
|
||||||
|
device["InternetGatewayDevice.ManagementServer.ConnReqJabberID"] || {}
|
||||||
|
).value || [""])[0];
|
||||||
username = ((
|
username = ((
|
||||||
device[
|
device[
|
||||||
"InternetGatewayDevice.ManagementServer.ConnectionRequestUsername"
|
"InternetGatewayDevice.ManagementServer.ConnectionRequestUsername"
|
||||||
@@ -72,6 +83,8 @@ export async function connectionRequest(
|
|||||||
udpConnectionRequestAddress = ((
|
udpConnectionRequestAddress = ((
|
||||||
device["Device.ManagementServer.UDPConnectionRequestAddress"] || {}
|
device["Device.ManagementServer.UDPConnectionRequestAddress"] || {}
|
||||||
).value || [""])[0];
|
).value || [""])[0];
|
||||||
|
connReqJabberId = ((device["Device.ManagementServer.ConnReqJabberID"] || {})
|
||||||
|
.value || [""])[0];
|
||||||
username = ((
|
username = ((
|
||||||
device["Device.ManagementServer.ConnectionRequestUsername"] || {}
|
device["Device.ManagementServer.ConnectionRequestUsername"] || {}
|
||||||
).value || [""])[0];
|
).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,
|
connectionRequestUrl,
|
||||||
authExp,
|
authExp,
|
||||||
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
|
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
|
||||||
@@ -169,6 +193,7 @@ export async function connectionRequest(
|
|||||||
await udpProm;
|
await udpProm;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ const options = {
|
|||||||
|
|
||||||
// Should probably never be changed
|
// Should probably never be changed
|
||||||
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 },
|
DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 },
|
||||||
|
|
||||||
|
XMPP_JID: { type: "string", default: "" },
|
||||||
|
XMPP_PASSWORD: { type: "string", default: "" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const allConfig: { [name: string]: string | number } = {};
|
const allConfig: { [name: string]: string | number } = {};
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import { Expression } from "./types";
|
|||||||
import * as auth from "./auth";
|
import * as auth from "./auth";
|
||||||
import * as extensions from "./extensions";
|
import * as extensions from "./extensions";
|
||||||
import * as debug from "./debug";
|
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(
|
async function extractAuth(
|
||||||
exp: Expression,
|
exp: Expression,
|
||||||
@@ -234,3 +238,87 @@ export async function udpConnectionRequest(
|
|||||||
}
|
}
|
||||||
client.close();
|
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");
|
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
|
||||||
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
|
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;
|
resource?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class XmppClient extends EventEmitter {
|
export default class XmppClient extends EventEmitter {
|
||||||
private _socket: net.Socket;
|
private _socket: net.Socket;
|
||||||
private _host: string;
|
private _host: string;
|
||||||
private _username: string;
|
private _username: string;
|
||||||
private _resource: string;
|
private _resource: string;
|
||||||
private _iqStanzaCallbacks: Map<
|
private _iqStanzaCallbacks: Map<
|
||||||
string,
|
string,
|
||||||
(r: { rawRes: string; res: Element }) => void
|
(err: Error, r?: { rawRes: string; res: Element }) => void
|
||||||
>;
|
>;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
@@ -384,7 +384,7 @@ export class XmppClient extends EventEmitter {
|
|||||||
const id = attrs.find((a) => a.name === "id");
|
const id = attrs.find((a) => a.name === "id");
|
||||||
if (id) {
|
if (id) {
|
||||||
const cb = this._iqStanzaCallbacks.get(id.value);
|
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);
|
this.emit("stanza", c, s);
|
||||||
@@ -397,6 +397,7 @@ export class XmppClient extends EventEmitter {
|
|||||||
private _onError(err: Error): void {
|
private _onError(err: Error): void {
|
||||||
this._socket.end();
|
this._socket.end();
|
||||||
this.emit("error", err);
|
this.emit("error", err);
|
||||||
|
for (const cb of this._iqStanzaCallbacks.values()) cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
send(msg: string): void {
|
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")
|
new Error("Did not receive IQ stanza response in a timely manner")
|
||||||
);
|
);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
this._iqStanzaCallbacks.set(id, (r) => {
|
this._iqStanzaCallbacks.set(id, (err, r) => {
|
||||||
|
this._iqStanzaCallbacks.delete(id);
|
||||||
clearTimeout(t);
|
clearTimeout(t);
|
||||||
resolve(Object.assign(r, { rawReq }));
|
if (err) reject(err);
|
||||||
|
else resolve(Object.assign(r, { rawReq }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user